文章目录
- MyBatis是什么?它和hibernate的区别有哪些?
- MyBatis的工作原理
- *MyBatis的核心组件
- SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession的作用域以及生命周期
- Mybatis框架的简单搭建
- MyBatis配置文件详解
- TypeHandler类型转换器
- MyBatis ObjectFactory(对象工厂)
- 配置文件environments
- Mybatis-Spring的简单整合
- select标签以及使用Map接口和Java Bean传递多个参数
- insert、update、delete和sql标签
- resultMap元素的结构及使用
- MyBatis关联查询(级联查询)
- MyBatis动态sql
- 参考内容:
MyBatis是什么?它和hibernate的区别有哪些?
MyBatis 的前身是 Apache 的开源项目 iBatis。MyBatis 几乎可以代替 JDBC,是一个支持普通 SQL 查询,存储过程和高级映射的基于 Java 的优秀持久层框架。
MyBatis 与 JDBC 相比,减少了 50% 以上的代码量。并且满足高并发和高响应的要求,所以它成为最流行的 Java 互联网持久框架。
在移动互联网时代,MyBatis 成为了目前互联网 Java 持久框架的首选。
MyBatis 本是 Apache 的一个开源项目——iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis。
MyBatis 是一个基于 Java 的持久层框架。MyBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
目前,Java 的持久层框架产品有许多,常见的有 Hibernate 和 MyBatis。
Hibernate 和 MyBatis 的区别
1)sql 优化方面
- Hibernate 不需要编写大量的 SQL,就可以完全映射,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作。但会多消耗性能。
- MyBatis 手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对大些。
2)开发方面
- MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系。
- Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。
3)Hibernate 优势
- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
4)Mybatis优势
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- MyBatis 容易掌握,而 Hibernate 门槛较高。
总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
所以对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate,而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。
MyBatis的工作原理
MyBatis 的版本可以通过“https://github.com/mybatis/mybatis-3/releases”网址下载。
MyBatis 的工作原理如图 2 所示。下面对图 2 中的每步流程进行说明。
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
*MyBatis的核心组件
MyBatis 的核心组件分为 4 个部分。
1)SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。
2)SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。
3)SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。
4)SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。
用一张图来展示 MyBatis 核心组件之间的关系,如图 1 所示。
注意,无论是映射器还是 SqlSession 都可以发送 SQL 到数据库执行,下面学习这些组件的用法。
1.SqlSessionFactory及其常见创建方式
使用 MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,而 MyBatis 提供了构造器 SqlSessionFactoryBuilder。
它提供了一个类 org.apache.ibatis.session.Configuration 作为引导,采用的是 Builder 模式。具体的分步则是在 Configuration 类里面完成的,当然会有很多内容,包括你很感兴趣的插件。
在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory
,也可以通过 Java 代码的形式去生成 SqlSessionFactory。
笔者强烈推荐采用 XML 的形式,因为代码的方式在需要修改的时候会比较麻烦。当配置了 XML 或者提供代码后,MyBatis 会读取配置文件,通过 Configuration 类对象构建整个 MyBatis 的上下文。
注意,SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。
一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,它们之间的关系如图 1 所示。
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。我们往往会采用单例模式处理它,下面讨论使用配置文件和 Java 代码两种形式去生成 SqlSessionFactory 的方法。
使用 XML 构建 SqlSessionFactory(推荐使用的方式)
首先,在 MyBatis 中的 XML 分为两类,一类是基础配置文件,通常只有一个,主要是配置一些最基本的上下文参数和运行环境;另一类是映射文件,它可以配置映射关系、SQL、参数等信息。
先看一份简易的基础配置文件,我们把它命名为 mybatis-config.xml,放在工程类路径下,其内容如下所示。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases><!--别名-->
<typeAliases alias="user" type="com.mybatis.po.User"/>
</typeAliases>
<!-- 数据库环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<!-- 连接数据库的URL -->
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="1128" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mybatis/mapper/UserMapper.xml" />
</mappers>
</configuration>
我们描述一下 MyBatis 的基础配置文件:
- 元素定义了一个别名 user,它代表着 com.mybatis.po.User 这个类。这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。
- 元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。
- 元素配置数据库,其中属性 type=“POOLED” 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。
- 元素代表引入的那些映射器,在谈到映射器时会详细讨论它。
有了基础配置文件,就可以用一段很简短的代码来生成 SqlSessionFactory 了,如下所示。
SqlSessionFactory factory = null;
String resource = "mybatis-config.xml";
InputStream is;
try {
InputStream is = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
首先读取 mybatis-config.xml,然后通过 SqlSessionFactoryBuilder 的 Builder 方法去创建 SqlSessionFactory。整个过程比较简单,而里面的步骤还是比较烦琐的,只是 MyBatis 采用了 Builder 模式为开发者隐藏了这些细节。这样一个 SqlSessionFactory 就被创建出来了。
采用 XML 创建的形式,信息在配置文件中,有利于我们日后的维护和修改,避免了重新编译代码,因此笔者推荐这种方式。
使用代码创建 SqlSessionFactory
虽然笔者不推荐使用这种方式,但是我们还是谈谈如何使用它。通过代码来实现与使用 XML 构建 SqlSessionFactory 一样的功能——创建 SqlSessionFactory,代码如下所示。
// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("1128");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;
注意代码中的注释,它和 XML 方式实现的功能是一致的,只是方式不太一样而已。但是代码冗长,如果发生系统修改,那么有可能需要重新编译代码才能继续,所以这不是一个很好的方式。
除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用这样的方式。
2.SqlSession简介
在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。
DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:
- 获取 Mapper 接口。
- 发送 SQL 给数据库。
- 控制数据库事务。
先来掌握它的创建方法,有了 SqlSessionFactory 创建的 SqlSession 就十分简单了,如下所示。
SqlSession sqlSession = SqlSessionFactory.openSession();
注意,SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL。
它就好像一家软件公司的商务人员,是一个门面,而实际干活的是软件工程师。在 MyBatis 中,真正干活的是 Executor,我们会在底层看到它。
SqlSession 控制数据库事务的方法,如下所示。
//定义 SqlSession
SqlSession sqlSession = null;
try {
// 打开 SqlSession 会话
sqlSession = SqlSessionFactory.openSession();
// some code...
sqlSession.commit(); // 提交事务
} catch (IOException e) {
sqlSession.rollback(); // 回滚事务
}finally{
// 在 finally 语句中确保资源被顺利关闭
if(sqlSession != null){
sqlSession.close();
}
}
这里使用 commit 方法提交事务,或者使用 rollback 方法回滚事务。因为它代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,那么数据库的连接资源就会很快被耗费光,整个系统就会陷入瘫痪状态,所以用 finally 语句保证其顺利关闭。
3.MyBatis实现映射器的2种方式:XML文件形式和注解形式
映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。
它可以配置以下内容:
- 描述映射规则。
- 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存-刷新等信息。
- 配置缓存。
- 提供动态 SQL。
本节阐述两种实现映射器的方式,XML 文件形式和注解形式。不过在此之前,先用以下 SQL 语句创建 role 表。
CREATE TABLE `role` (
`id` bigint(20) NOT NULL,
`role_name` varchar(20) DEFAULT NULL,
`note` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
并且定义一个 POJO,它十分简单,如下所示。
package com.mybatis.po;
public class Role {
private Long id;
private String roleName;
private String note;
/**setter and getter**/
}
映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。
注意,开发只是一个接口,而不是一个实现类。初学者可能会产生一个很大的疑问,那就是接口不是不能运行吗?
是的,接口不能直接运行。MyBatis 运用了动态代理技术使得接口能运行起来
,入门阶段只要懂得 MyBatis 会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑即可。
用 XML 实现映射器(推荐使用)
用 XML 定义映射器分为两个部分:接口和 XML。先定义一个映射器接口,如下所示。
package com.mybatis.mapper;
import com.mybatis.po.Role;
public interface RoleMapper {
public Role getRole(Long id);
}
在用 XML 方式创建 SqlSession 的配置文件中有这样一段代码:
它的作用就是引入一个 XML 文件。用 XML 方式创建映射器,如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.mapper.RoleMapper">
<select id="getRole" parameterType="long" resultType="role">
SELECT id,role_name as roleName,note FROM role WHERE id =#{id}
</select>
</mapper>
有了这两个文件,就完成了一个映射器的定义。XML 文件还算比较简单,我们稍微讲解一下:
元素中的属性 namespace 所对应的是一个接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。
元素表明这是一条查询语句,而属性 id 标识了这条 SQL,属性 parameterType=“long” 说明传递给 SQL 的是一个 long 型的参数,而 resultType=“role” 表示返回的是一个 role 类型的返回值。而 role 是之前配置文件 mybatis-config.xml 配置的别名,指代的是 com.mybatis.po.Role。
这条 SQL 中的 #{id} 表示传递进去的参数。
注意,我们并没有配置 SQL 执行后和 role 的对应关系,它是如何映射的呢?
其实这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。
这里 SQL 返回的列名 id 和 note 是可以和之前定义的 POJO 的属性对应起来的,而表里的列 role_name 通过 SQL 别名的改写,使其成为 roleName,也是和 POJO 对应起来的,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。
注解实现映射器
除 XML 方式定义映射器外,还可以采用注解方式定义映射器,它只需要一个接口就可以通过 MyBatis 的注解来注入 SQL,如下所示。
package com.mybatis.mapper;
import org.apache.ibatis.annotations.Select;
import com.mybatis.po.Role;
public interface RoleMapper2 {
@Select("select id,role_name as roleName,note from t_role where id=#{id}")
public Role getRole(Long id);
}
这完全等同于 XML 方式创建映射器。也许你会觉得使用注解的方式比 XML 方式要简单得多。如果它和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,所以 MyBatis 官方推荐使用的是 XML 方式,因此本教程以 XML 方式为主讨论 MyBatis 的应用。
在工作和学习中,SQL 的复杂度远远超过我们现在看到的 SQL,比如下面这条 SQL。
select * from t_user u
left join t_user_role ur on u.id = ur.user_id
left join t_role r on ur.role_id = r.id
left join t_user_info ui on u.id = ui.user_id
left join t_female_health fh on u.id = fh.user_id
left join t_male_health mh on u.id = mh.user_id
where u.user_name like concat('%', ${userName},'%')
and r.role_name like concat('%', ${roleName},'%')
and u.sex = 1
and ui.head_image is not null;
显然这条 SQL 比较复杂,如果放入 @Select 中会明显增加注解的内容。如果把大量的 SQL 放入 Java 代码中,显然代码的可读性也会下降。
如果同时还要考虑使用动态 SQL,比如当参数 userName 为空,则不使用 u.user_name like concat(’%’, u s e r N a m e , ′ {userName},'%')作为查询条件;当 roleName 为空,则不使用 r.role_name like concat('%', userName,′{roleName},’%’)作为查询条件,但是还需要加入其他的逻辑,这样就使得这个注解更加复杂了,不利于日后的维护和修改。
此外,XML 可以相互引入,而注解是不可以的,所以在一些比较复杂的场景下,使用 XML 方式会更加灵活和方便。所以大部分的企业都是以 XML 为主,本教程也会保持一致,以 XML 方式来创建映射器。当然在一些简单的表和应用中使用注解方式也会比较简单。
这个接口可以在 XML 中定义,我们仿造在 mybatis-config.xml 中配置 XML 语句:
<mapper resource="com/mybatis/mapper/RoleMapper.xml" />
把它修改为下面的形式即可。
<mapper resource="com/mybatis/mapper/RoleMapper2" />
也可以使用 configuration 对象注册这个接口,比如:
configuration.addMapper(RoleMapper2.class);
4.MyBatis执行SQL的两种方式:SqlSession和Mapper接口
本节主要介绍 MyBatis 执行 SQL 语句的两种方式和它们的区别。
SqlSession 发送 SQL
有了映射器就可以通过 SqlSession 发送 SQL 了。我们以 getRole 这条 SQL 为例看看如何发送 SQL。
Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);
selectOne 方法表示使用查询并且只返回一个对象,而参数则是一个 String 对象和一个 Object 对象。这里是一个 long 参数,long 参数是它的主键。
String 对象是由一个命名空间加上 SQL id 组合而成的,它完全定位了一条 SQL,这样 MyBatis 就会找到对应的 SQL。如果在 MyBatis 中只有一个 id 为 getRole 的 SQL,那么也可以简写为:
Role role = (Role)sqlSession.selectOne("getRole",1L);
这是 MyBatis 前身 iBatis 所留下的方式。
用 Mapper 接口发送 SQL(推荐使用)
SqlSession 还可以获取 Mapper 接口,通过 Mapper 接口发送 SQL,如下所示。
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果。
对比两种发送 SQL 方式
上面分别展示了 MyBatis 存在的两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。笔者建议采用 SqlSession 获取 Mapper 的方式,理由如下:
使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。使用 Mapper 接口,类似 roleMapper.getRole(1L)则是完全面向对象的语言,更能体现业务的逻辑。
使用 Mapper.getRole(1L)方式,IDE 会提示错误和校验,而使用 sqlSession.selectOne(“getRole”,1L)语法,只有在运行中才能知道是否会产生错误。
目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单,所以本教程使用 Mapper 接口的方式讨论 MyBatis。
SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession的作用域以及生命周期
所谓生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被 Java 虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命周期。
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
。
SqlSessionFactory
SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域
。
SqlSession
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。
所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
所以 SqlSession 的最佳的作用域是请求或方法作用域
。
Mapper
Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它
。
以上,我们讨论了 MyBatis 组件的生命周期,如图 1 所示。
Mybatis框架的简单搭建
Mybatis框架的简单搭建:
https://blog.csdn.net/Bonport/article/details/104968969
MyBatis配置文件详解
MyBatis 配置文件并不复杂,它所有的元素如下所示。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 配置 -->
<properties /><!-- 属性 -->
<settings /><!-- 设置 -->
<typeAliases /><!-- 类型命名 -->
<typeHandlers /><!-- 类型处理器 -->
<objectFactory /><!-- 对象工厂 -->
<plugins /><!-- 插件 -->
<environments><!-- 配置环境 -->
<environment><!-- 环境变量 -->
<transactionManager /><!-- 事务管理器 -->
<dataSource /><!-- 数据源 -->
</environment>
</environments>
<databaseIdProvider /><!-- 数据库厂商标识 -->
<mappers /><!-- 映射器 -->
</configuration>
但是需要注意的是,MyBatis 配置项的顺序不能颠倒。如果颠倒了它们的顺序,那么在 MyBatis 启动阶段就会发生异常,导致程序无法运行。
本节的任务是了解 MyBatis 配置项的作用,其中 properties、settings、typeAliases、typeHandler、plugin、environments、mappers 是常用的内容。
先不讨论 plugin(插件)元素的使用,在进一步学习 MyBatis 的许多底层内容和设计后我们才会学习它。MyBatis 中 objectFactory 和 databaseIdProvider 不常用。
properties元素
properties 属性可以给系统配置一些运行参数,可以放在 XML 文件或者 properties 文件中,而不是放在 Java 编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis 提供了 3 种方式让我们使用 properties,它们是:
- property 子元素。
- properties 文件。
- 程序代码传递。
property 子元素
以下面代码为基础,使用 property 子元素将数据库连接的相关配置进行改写,如下所示。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="1128" />
</properties>
<typeAliases>
<typeAlias alias="role" type="com.mybatis.po.Role"/>
</typeAliases>
<!--数据库环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="1128" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="com/mybatis/mapper/RoleMapper.xml" />
</mappers>
</configuration>
这里使用了元素 下的子元素 定义,用字符串 database.username 定义数据库用户名,然后就可以在数据库定义中引入这个已经定义好的属性参数,如 ${database.username},这样定义一次就可以到处引用了。但是如果属性参数有成百上千个,显然使用这样的方式不是一个很好的选择,这个时候可以使用 properties 文件。
使用 properties 文件
使用 properties 文件是比较普遍的方法,一方面这个文件十分简单,其逻辑就是键值对应,我们可以配置多个键值放在一个 properties 文件中,也可以把多个键值放到多个 properties 文件中,这些都是允许的,它方便日后维护和修改。
我们创建一个文件 jdbc.properties 放到 classpath 的路径下,如下所示。
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis
database.username=root
database.password=1128
在 MyBatis 中通过 的属性 resource 来引入 properties 文件。
<properties resource="jdbc.properties"/>
也可以按 ${database.username} 的方法引入 properties 文件的属性参数到 MyBatis 配置文件中。这个时候通过维护 properties 文件就可以维护我们的配置内容了。
使用程序传递方式传递参数
在真实的生产环境中,数据库的用户密码是对开发人员和其他人员保密的。运维人员为了保密,一般都需要把用户和密码经过加密成为密文后,配置到 properties 文件中。
对于开发人员及其他人员而言,就不知道其真实的用户密码了,数据库也不可能使用已经加密的字符串去连接,此时往往需要通过解密才能得到真实的用户和密码了。
现在假设系统已经为提供了这样的一个 CodeUtils.decode(str)进行解密,那么我们在创建 SqlSessionFactory 前,就需要把用户名和密码解密,然后把解密后的字符串重置到 properties 属性中,如下所示。
String resource = "mybatis-config.xml";
InputStream inputStream;
Inputstream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.password");
//解密用户和密码,并在属性中重置
props.put("database.username", CodeUtils.decode(username));
props.put ("database.password", CodeUtils.decode(password));
inputstream = Resources.getResourceAsStream(resource);
//使用程序传递的方式覆盖原有的properties属性参数
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream, props);
首先使用 Resources 对象读取了一个 jdbc.properties 配置文件,然后获取了它原来配置的用户和密码,进行解密并重置,最后使用 SqlSessionFactoryBuilder 的 build 方法,传递多个 properties 参数来完成。
这将覆盖之前配置的密文,这样就能连接数据库了,同时也满足了运维人员对数据库用户和密码安全的要求。
settings属性配置详解
在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。
settings属性具体配置项:
http://c.biancheng.net/view/4324.html
settings 的配置项很多,但是真正用到的不会太多,我们把常用的配置项研究清楚就可以了,比如关于缓存的 cacheEnabled,关于级联的 lazyLoadingEnabled 和 aggressiveLazy Loading,关于自动映射的 autoMappingBehavior 和 mapUnderscoreToCamelCase,关于执行器类型的 defaultExecutorType 等。
这里给出一个全量的配置样例,如下所示。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
typeAliases(别名)详解
由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。
在 MyBatis 中别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。注意,在 MyBatis 中别名不区分大小写。
在 MyBatis 中别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。注意,在 MyBatis 中别名不区分大小写。
自定义别名
由于现实中,特别是大型互联网系统中存在许多对象,比如用户(User)这个对象有时候需要大量重复地使用,因此 MyBatis 也提供了用户自定义别名的规则。我们可以通过 TypeAliasRegistry 类的 registerAlias 方法注册,也可以采用配置文件或者扫描方式来自定义它。
使用配置文件定义很简单:
<typeAliases><!--别名-->
<typeAlias alias="role" type="com.mybatis.po.Role"/>
<typeAlias alias="role" type="com.mybatis.po.User"/>
</typeAliases>
这样就可以定义一个别名了。如果有很多类需要定义别名,那么用这样的方式进行配置可就不那么轻松了。MyBatis 还支持扫描别名。比如上面的两个类都在包 com.mybatis.po 之下,那么就可以定义为:
<typeAliases><!--别名-->
<package name="com.mybatis.po"/>
</typeAliases>
这样 MyBatis 将扫描这个包里面的类,将其第一个字母变为小写作为其别名,比如类 Role 的别名会变为 role,而 User 的别名会变为 user。使用这样的规则,有时候会出现重名。
比如 com.mybatis.po.User 这个类,MyBatis 还增加了对包 com.mybatis.po 的扫描,那么就会出现异常,这个时候可以使用 MyBatis 提供的注解 @Alias(“user3”)进行区分,如下所示。
package com.mybatis.po;
@Alias("user3")
public Class User {
......
}
这样就能够避免因为别名重名导致的扫描失败的问题。
系统定义别名
在 MyBatis 的初始化过程中,系统自动初始化了一些别名,如下表所示。
如果需要使用对应类型的数组型,要看其是否能支持数据,如果支持只需要使用别名加[]即可,比如 _int 数组的别名就是 _int[]。而类似 list 这样不支持数组的别名,则不能那么写。
有时候要通过代码来实现注册别名,让我们看看 MyBatis 是如何初始化这些别名的,如下所示。
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
......
registerAlias("byte[]",Byte[].class); registerAlias("long[]",Long[].class);
......
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
所以使用 TypeAliasRegistry 的 registerAlias 方法就可以注册别名了。一般是通过 Configuration 获取 TypeAliasRegistry 类对象,其中有一个 getTypeAliasRegistry 方法可以获得别名,如 configuration.getTypeAliasRegistry()。
然后就可以通过 registerAlias 方法对别名注册了。而事实上 Configuration 对象也对一些常用的配置项配置了别名,如下所示。
//事务方式别名
typeAliasRegistry.registerAlias("JDBC",JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class);
//数据源类型别名
typeAliasRegistry.registerAlias("JNDI",JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED",
PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED",UnpooledDataSourceFactory.class);
//缓存策略别名
typeAliasRegistry.registerAlias("PERPETUAL",PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO",FifoCache.class);
typeAliasRegistry.registerAlias("LRU",LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
//数据库标识别名
typeAliasRegistry.registerAlias("DB_VENDOR",
VendorDatabaseIdProvider.class);
//语言驱动类别名
typeAliasRegistry.registerAlias("XML",XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW",RawLanguageDriver.class);
//日志类别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGTNG",JakartmCommonsLogginglmpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING",NoLoggingImpl.class);
//动态代理别名
typeAliasRegistry.registerAlias("CGLIB",CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST",JavassistProxyFactory.class);
这些配置为的是让我们更容易配置 MyBatis 的相关信息。以上就是 MyBatis 系统定义的别名,我们在使用的时候,不要重复命名,导致出现其他问题。
TypeHandler类型转换器
在 JDBC 中,需要在 PreparedStatement 对象中设置那些已经预编译过的 SQL 语句的参数。执行 SQL 后,会通过 ResultSet 对象获取得到数据库的数据,而这些 MyBatis 是根据数据的类型通过 typeHandler 来实现的。
在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,那么 typeHandler 的作用就是承担 jdbcType 和 javaType 之间的相互转换。
如图 1 所示。在很多情况下我们并不需要去配置 typeHandler、jdbcType、javaType,因为 MyBatis 会探测应该使用什么类型的 typeHandler 进行处理,但是有些场景无法探测到。
对于那些需要使用自定义枚举的场景,或者数据库使用特殊数据类型的场景,可以使用自定义的 typeHandler 去处理类型之间的转换问题。
和别名一样,在 MyBatis 中存在系统定义 typeHandler 和自定义 typeHandler。MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。系统提供的 typeHandler 能覆盖大部分场景的要求,但是有些情况下是不够的,比如我们有特殊的转换规则,枚举类就是这样。
讲解这 4 种 typeHandler:
MyBatis系统定义的TypeHandler
MyBatis自定义TypeHandler
MyBatis自定义TypeHandler处理枚举
BlobTypeHandler读取Blob类型字段
MyBatis ObjectFactory(对象工厂)
当创建结果集时,MyBatis 会使用一个对象工厂来完成创建这个结果集实例。在默认的情况下,MyBatis 会使用其定义的对象工厂——DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)来完成对应的工作。
MyBatis 允许注册自定义的 ObjectFactory。如果自定义,则需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory,并给予配置。
在大部分的情况下,我们都不需要自定义返回规则,因为这些比较复杂而且容易出错,在更多的情况下,都会考虑继承系统已经实现好的 DefaultObjectFactory ,通过一定的改写来完成我们所需要的工作。
配置文件environments
在 MyBatis 中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言只需要配置其中的一个就可以了。
它下面又分为两个可配置的元素:事务管理器(transactionManager)、数据源(dataSource)。
http://c.biancheng.net/view/4352.html
在实际的工作中,大部分情况下会采用 Spring 对数据源和数据库的事务进行管理。
Mybatis-Spring的简单整合
下面通过一个实例实现 MyBatis 与 Spring 的整合,具体实现过程如下:
https://blog.csdn.net/Bonport/article/details/104986335
select标签以及使用Map接口和Java Bean传递多个参数
在 SQL 映射文件中 元素用于映射 SQL 的 select 语句,其示例代码如下:
<!--根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer" resultType="com.mybatis.po.MyUser">
select * from user where uid = #{uid}
</select>
在上述示例代码中,id 的值是唯一标识符,它接收一个 Integer 类型的参数,返回一个 MyUser 类型的对象,结果集自动映射到 MyUser 属性。
元素除了有上述示例代码中的几个属性以外,还有一些常用的属性,如表 所示。
使用 Map 接口传递多个参数
在实际开发中,查询 SQL 语句经常需要多个参数,例如多条件查询。当传递多个参数时, 元素的 parameterType 属性值的类型是什么呢?在 MyBatis 中允许 Map 接口通过键值对传递多个参数。
假设数据操作接口中有个实现查询陈姓男性用户信息功能的方法:
public List<MyUser> selectAllUser(Map<String,Object> param);
此时,传递给映射器的是一个 Map 对象,使用它在 SQL 文件中设置对应的参数,对应 SQL 文件的代码如下:
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
在上述 SQL 文件中,参数名 u_name 和 u_sex 是 Map 的 key。
为了测试该示例,首先创建一个 Web 应用 mybatisDemo02,将 mybatisDemo01 应用的所有 JAR 包复制到 /WEB-INF/lib 下,同时将 mybatisDemo01 应用的 src 目录下的所有包和文件复制到 mybatisDemo02 应用的 src 目录下。
然后将 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中的“查询所有用户信息”的代码片段修改为上述“查询陈姓男性用户信息”的代码片段,最后将 com.controller 包中 UserController 的代码简单修改即可运行测试类了。
com.controller 包中 UserController 的代码片段如下:
@Controller("UserController")
public class UserController {
private UserDao userDao;
public void test(){
...
//查询多个用户
Map<String,Object> map = new HashMap<>();
map.put("u_name","陈");
map.put("u_sex","男");
List<MyUser> list = userDao.seleceAllUser(map);
for(MyUser myUser : list) {
System.out.println(myUser);
}
...
}
}
Map 是一个键值对应的集合,使用者要通过阅读它的键才能了解其作用。另外,使用 Map 不能限定其传递的数据类型,所以业务性不强,可读性较差。如果 SQL 语句很复杂,参数很多,使用 Map 将很不方便。幸运的是,MyBatis 还提供了使用 Java Bean 传递多个参数的形式。
使用 Java Bean 传递多个参数
首先在 myBatisSemo02 应用的 src 目录下创建一个名为 com.pojo 的包,在包中创建一个 POJO 类 SeletUserParam,代码如下:
package com.pojo;
public class SeletUserParam {
private String u_name;
private String u_sex;
// 此处省略setter和getter方法
}
接着将 Dao 接口中的 selectAllUser 方法修改为如下:
public List<MyUser> selectAllUser(SelectUserParam param);
然后将 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中的“查询陈姓男性用户信息”的代码修改为如下:
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
select * from user
where uname like concat('%',#{u_name},'%')
and usex=#{u_sex}
</select>
最后将 com.controller 包中 UserController 的“查询多个用户”的代码片段做如下修改:
SeletUserParam su = new SelectUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<MyUser> list = userDao.selectAllUser(su);
for (MyUser myUser : list) {
System.out.println(myUser);
}
在实际应用中是选择 Map 还是选择 Java Bean 传递多个参数应根据实际情况而定,如果参数较少,建议选择 Map;如果参数较多,建议选择 Java Bean。
insert、update、delete和sql标签
insert元素
insert元素用于映射插入语句,MyBatis 执行完一条插入语句后将返回一个整数表示其影响的行数。它的属性与select元素的属性大部分相同,在本节讲解它的几个特有属性。
- keyProperty:该属性的作用是将插入或更新操作时的返回值赋给 PO 类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。
- keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第 1 列时需要设置。如果是联合主键,可以将多个值用逗号隔开。
- useGeneratedKeys:该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取由数据库内部产生的主键,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false。
1)主键(自动递增)回填
MySQL、SQL Server 等数据库的表格可以采用自动递增的字段作为主键,有时可能需要使用这个刚刚产生的主键,用于关联其他业务。
首先为 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中 id 为 addUser 的 insert元素添加 keyProperty 和 useGeneratedKeys 属性,具体代码如下:
<!--添加一个用户,成功后将主键值返回填给uid(po的属性)-->
<insert id="addUser" parameterType="com.po.MyUser" keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
然后在 com.controller 包的 UserController 类中进行调用,具体代码如下:
// 添加一个用户
MyUser addmu = new MyUser();
addmu.setUname("陈恒");
addmu.setUsex("男");
int add = userDao.addUser(addmu);
System.out.println("添加了" + add + "条记录");
System.out.println("添加记录的主键是" + addmu.getUid());
2)自定义主键
如果在实际工程中使用的数据库不支持主键自动递增(例如 Oracle),或者取消了主键自动递增的规则,可以使用 MyBatis 的 selectKey元素来自定义生成主键。具体配置示例代码如下:
<!-- 添加一个用户,#{uname}为 com.mybatis.po.MyUser 的属性值 -->
<insert id="insertUser" parameterType="com.po.MyUser">
<!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
<selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
select if(max(uid) is null,1,max(uid)+1) as newUid from user)
</selectKey>
insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>
在执行上述示例代码时,selectKey 元素首先被执行,该元素通过自定义的语句设置数据表的主键,然后执行插入语句。
selectKey 元素的 keyProperty 属性指定了新生主键值返回给 PO 类(com.po.MyUser)的哪个属性。
- order 属性可以设置为 BEFORE 或 AFTER。
- BEFORE 表示先执行 selectKey 元素然后执行插入语句。
- AFTER 表示先执行插入语句再执行 selectKey 元素。
update与delete元素
update 和 delete 元素比较简单,它们的属性和 insert元素、select 元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。配置示例代码如下:
<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.po.MyUser">
update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid = #{uid}
</delete>
sql 元素
sql 元素的作用在于可以定义 SQL 语句的一部分(代码片段),以方便后面的 SQL 语句引用它,例如反复使用的列名。
在 MyBatis 中只需使用 元素编写一次便能在其他元素中引用它。配置示例代码如下:
<sql id="comColumns">id,uname,usex</sql>
<select id="selectUser" resultType="com.po.MyUser">
select <include refid="comColumns"> from user
</select>
在上述代码中使用 include元素的 refid 属性引用了自定义的代码片段。
resultMap元素的结构及使用
< resultMap > 元素表示结果映射集,是 MyBatis 中最重要也是最强大的元素,主要用来定义映射规则、级联的更新以及定义类型转化器等。
resultMap 元素的结构
resultMap元素包含了一些子元素,结构如下:
<resultMap id="" type="">
<constructor><!-- 类再实例化时用来注入结果到构造方法 -->
<idArg/><!-- ID参数,结果为ID -->
<arg/><!-- 注入到构造方法的一个普通结果 -->
</constructor>
<id/><!-- 用于表示哪个列是主键 -->
<result/><!-- 注入到字段或JavaBean属性的普通结果 -->
<association property=""/><!-- 用于一对一关联 -->
<collection property=""/><!-- 用于一对多、多对多关联 -->
<discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
<case value=""/><!-- 基于某些值的结果映射 -->
</discriminator>
</resultMap>
- < resultMap> 元素的 type 属性表示需要的 POJO,id 属性是 resultMap 的唯一标识。
- 子元素 < constructor> 用于配置构造方法(当 POJO 未定义无参数的构造方法时使用)。
- 子元素 < id> 用于表示哪个列是主键。
- 子元素 < result> 用于表示POJO和数据表普通列的映射关系。
- 子元素 < association>、< collection> 和 用在级联的情况下。关于级联的问题比较复杂,后面教程会详细讲解。
一条查询 SQL 语句执行后将返回结果,而结果可以使用 Map 存储,也可以使用 POJO 存储
。
使用 Map 存储结果集
任何 select 语句都可以使用 Map 存储结果,示例代码如下:
<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
select * from user
</select>
测试上述 SQL 配置文件的过程如下:
首先在 com.dao.UserDao 接口中添加以下接口方法。
public List<Map<String,Object>> selectAllUserMap();
然后在 com.controller 包的 UserController 类中调用接口方法,具体代码如下。
// 查询所有用户信息存到Map中
List<Map<String, Object>> lmp = userDao.selectAllUserMap();
for (Map<String, Object> map : lmp) {
System.out.println(map);
}
上述 Map 的 key 是 select 语句查询的字段名(必须完全一样),而 Map 的 value 是查询返回结果中字段对应的值,一条记录映射到一个 Map 对象中。Map 用起来很方便,但可读性稍差,有的开发者不太喜欢使用 Map,更多时候喜欢使用 POJO 的方式。
使用POJO存储结果集
有的开发者喜欢使用 POJO 的方式存储结果集,一方面可以使用自动映射,例如使用 resultType 属性,但有时候需要更为复杂的映射或级联,这时候就需要使用 < select> 元素的 resultMap 属性配置映射集合。具体步骤如下:
1)创建 POJO 类
在 myBatisDemo02 应用的 com.pojo 包中创建 POJO 类 MapUser。MapUser 类的代码如下:
package com.pojo;
public class MapUser {
private Integer m_uid;
private String m_uname;
private String m_usex;
// 此处省略setter和getter方法
@Override
public String toString() {
return "User[uid=" + m_uid + ",uname=" + m_uname + ",usex=" + m_usex
+ "]";
}
}
2)配置 < resultMap> 元素
在 SQL 映射文件 UserMapper.xml 中配置 < resultMap> 元素,其属性 type 引用 POJO 类。具体配置如下:
<!--使用自定义结果集类型-->
< resultMap type="com.pojo.MapUser" id="myResult">
<!-- property 是 com.pojo.MapUser 类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表-->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>
3)配置< select>元素
在 SQL 映射文件 UserMapper.xml 中配置 元素,其属性 resultMap 引用了 元素的 id。具体配置如下:
<!-- 使用自定义结果集类型查询所有用户 -->
<select id="selectResultMap" resultMap="myResult">
select * from user
</select>
4)添加接口方法
在 com.dao.UserDao 接口中添加以下接口方法:
public List< MapUser> selectResultMap();
5)调用接口方法
在 com.controller 包的 UserController 类中调用接口方法,具体代码如下:
// 使用resultMap映射结果集
List<MapUser> listResultMap = userDao.selectResultMap();
for (MapUser myUser : listResultMap) {
System.out.println(myUser);
}
MyBatis关联查询(级联查询)
级联关系是一个数据库实体的概念,有 3 种级联关系,分别是一对一级联、一对多级联以及多对多级联。
级联的优点是获取关联数据十分方便,但是级联过多会增加数据库系统的复杂度,同时降低系统的性能。
在实际开发中要根据实际情况判断是否需要使用级联。更新和删除的级联关系很简单,由数据库内在机制即可完成。本节只讲述级联查询的相关实现。
如果表 A 中有一个外键引用了表 B 的主键,A 表就是子表,B 表就是父表。当查询表 A 的数据时,通过表 A 的外键将表 B 的相关记录返回,这就是级联查询。例如,当查询一个人的信息时,同时根据外键(身份证号)将他的身份证信息返回。
一对一关联查询
在 MyBatis 中,通过 元素的子元素 处理这种一对一级联关系。
在 元素中通常使用以下属性。
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)。
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
一对多关联查询
实现mybatis一对多的关联
解决一个Person有多张IdCard
点击查看》》》一对多关联查询《《《
重点:
在IdCard类中添加 person.id 属性
在person类中添加List< IdCard> cardList 属性
在PersonMapper.xml中
< resultMap>
…
< collection ofType="…IdCArd">
< /collection>
</ resultMap>
一对多关联查询
其实,MyBatis 没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。
例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系,使用一个中间表(订单记录表)就可以将多对多级联转换成两个一对多的关系。
如在,product类中添加属性
// 多对多中的一个一对多
private List<Orders> orders;
在Orders类中添加属性:
// 多对多中的另一个一对多
private List<Product> products;
MyBatis动态sql
MyBatis 的动态 SQL 元素与 JSTL 或 XML 文本处理器相似,常用 < if>、< choose>、< when>、< otherwise>、< trim>、< where>、< set>、< foreach> 和 等元素。
if标签:条件判断
动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分,所以在 MyBatis 中 元素是最常用的元素,它类似于 Java 中的 if 语句。
<!--使用 if 元素根据条件动态查询用户信息-->
<select id="selectUserByIf" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user where 1=1
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if >
<if test="usex !=null and usex !=''">
and usex=#{usex}
</if >
</select>
choose、when、otherwise标签
有些时候不想用到所有的条件语句,而只想从中择取一二,针对这种情况,MyBatis 提供了 < choose> 元素,它有点像 Java 中的 switch 语句。
<!--使用choose、when、otherwise元素根据条件动态查询用户信息-->
<select id="selectUserByChoose" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
select * from user where 1=1
<choose>
<when test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</when>
<when test="usex!=null and usex!=''">
and usex=#{usex}
</when>
<otherwise>
and uid > 10
</otherwise>
</choose>
</select>
trim、where、set标签
< trim>元素
< trim> 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix。
可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides。正因为 < trim> 元素有这样的功能,所以也可以非常简单地利用 < trim> 来代替 < where> 元素的功能。
<!--使用trim元素根据条件动态查询用户信息-->
<select id="selectUserByTrim" resultType="com.po.MyUser"parameterType="com.po.MyUser">
select * from user
<trim prefix="where" prefixOverrides = "and | or">
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex!=null and usex!=''">
and usex=#{usex}
</if>
</trim>
</select>
< where> 元素
< where> 元素的作用是会在写入 < where> 元素的地方输出一个 where 语句,另外一个好处是不需要考虑 < where> 元素里面的条件输出是什么样子的,MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。
当然如果是以 or 开头的,MyBatis 也会把它忽略;此外,在 < where> 元素中不需要考虑空格的问题,MyBatis 将智能加上。
<!--使用where元素根据条件动态查询用户信息-->
<select id="selectUserByWhere" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user
<where>
<if test="uname != null and uname ! = ''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex != null and usex != '' ">
and usex=#{usex}
</if >
</where>
</select>
< set>元素
在动态 update 语句中可以使用 < set> 元素动态更新列。
<!--使用set元素动态修改一个用户-->
<update id="updateUserBySet" parameterType="com.po.MyUser">
update user
<set>
<if test="uname!=null">uname=#{uname}</if>
<if test="usex!=null">usex=#{usex}</if>
</set>
where uid=#{uid}
</update>
foreach标签
< foreach> 元素主要用在构建 in 条件中,它可以在 SQL 语句中迭代一个集合。
< foreach> 元素的属性主要有 item、index、collection、open、separator、close。
- item 表示集合中每一个元素进行迭代时的别名。
- index 指定一个名字,用于表示在迭代过程中每次迭代到的位置。
- open 表示该语句以什么开始。
- separator 表示在每次进行迭代之间以什么符号作为分隔符。
- close 表示以什么结束。
在使用 < foreach> 元素时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
- 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
- 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
- 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 -key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
<!--使用foreach元素查询用户信息-->
<select id="selectUserByForeach" resultType="com.po.MyUser" parameterType=
"List">
select * from user where uid in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
# {item}
</foreach>
</select>
bind标签
在进行模糊查询时,如果使用“${}”拼接字符串,则无法防止 SQL 注入问题。如果使用字符串拼接函数或连接符号,但不同数据库的拼接函数或连接符号不同。
例如 MySQL 的 concat 函数、Oracle 的连接符号“||”,这样 SQL 映射文件就需要根据不同的数据库提供不同的实现,显然比较麻烦,且不利于代码的移植。幸运的是,MyBatis 提供了 元素来解决这一问题。
<!--使用bind元素进行模糊查询-->
<select id="selectUserByBind" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
<!-- bind 中的 uname 是 com.po.MyUser 的属性名-->
<bind name="paran_uname" value="'%' + uname + '%'"/>
select * from user where uname like #{paran_uname}
</select>
参考内容:
从C语言中文网学到了很多,网址http://c.biancheng.net/mybatis/