XML 映射文件
1. 概述
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache
– 给定命名空间的缓存配置。cache-ref
– 其他命名空间缓存配置的引用。resultMap
– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。parameterMap
– 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。sql
– 可被其他语句引用的可重用语句块。insert
– 映射插入语句update
– 映射更新语句delete
– 映射删除语句select
– 映射查询语句
2. 增删改查实例
1.userCrudMapper 接口
public interface UserCrudMapper {
//mybatis 允许增、删、改直接定义Integer、Long、Boolean返回值
public int addUser(User user);
public int deleteUser(User user);
public int updateUser(User user);
public User getUser(Integer id);
public List<User> getAllUsers();
}
2.sql 映射文件 UserCrudMapper.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.chen.dao.UserCrudMapper">
<select id="getUser" parameterType="int" resultType="user">
SELECT * FROM users where id = #{id}
</select>
<insert id="addUser" parameterType="user">
INSERT INTO users (name,age) VALUES (#{name},#{age})
</insert>
<update id="updateUser" parameterType="user">
UPDATE users SET name=#{name},age=#{age} WHERE id=#{id}
</update>
<delete id="deleteUser" parameterType="user">
DELETE FROM users WHERE id=#{id}
</delete>
<select id="getAllUsers" resultType="user">
SELECT * FROM users
</select>
</mapper>
3.在 mybatis 中配置
<mappers>
<mapper resource="UserCrudMapper.xml"/>
</mappers>
4.测试方法
getSqlSessionFactory 方法
private SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
test 方法
@Test
public void testCrud() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//默认为手动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
try {
UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
System.out.println(mapper.getAllUsers());
System.out.println(mapper.addUser(new User(0, "test", 10)));
System.out.println(mapper.updateUser(new User(6,"ga",21)));
System.out.println(mapper.getUser(6));
System.out.println(mapper.deleteUser(6));
} finally {
sqlSession.close();
}
}
3. INSERT 获取自增主键值
MyBatis 使用 JDBC 的 getGeneratedKeys
(仅对 insert 和 update 有用) 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
而 keyProperty
唯一标记一个属性,MyBatis 会通过 getGeneratedKeys
的返回值或者通过 insert 语句的 selectKey
子元素设置它的键值,默认:unset
。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
sql 映射文件:
<insert id="addUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name,age) VALUES (#{name},#{age})
</insert>
测试方法:
@Test
public void testGeneratedKeys() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
try {
UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
User user = new User(0, "test", 10);
System.out.println(mapper.addUser(user));
//此时可以打印出主键值
System.out.println(user.getId());
} finally {
sqlSession.close();
}
}
4. 参数处理
4.1. 单个参数
mybatis 不会做特殊处理,直接通过 #{参数名}
来取出参数值。
4.2. 多个参数
多个参数时候(例如:接口方法为public User getUserByIdAndName(Integer id,String name)
),如果像单个参数时候直接通过#{参数名}
来取出参数值,会报出Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
之类的异常。
这是因为 mybatis 会将多个参数封装成 map ,而 map 的 key 是 param1 ... paramN
或者是参数的索引 ,而 value 即传入的参数值。#{}
就是从 map 中获取指定 key 值。
即此时的 sql 映射文件修改为:
<select id="getUserByIdAndName" resultType="user">
SELECT * FROM users where id = #{param1} and name =#{param2}
</select>
或者
<select id="getUserByIdAndName" resultType="user">
SELECT * FROM users where id = #{0} and name =#{1}
</select>
4.3. @Param 注解
为参数使用 @Param
起一个名字,mybatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字。
例如 dao 接口的方法为:public User getUserByIdAndName(@Param("id") Integer id, @Param("name") String name)
。
此时 sql 映射文件修改为
<select id="getUserByIdAndName" resultType="user">
SELECT * FROM users where id = #{id} and name =#{name}
</select>
4.4. POJO参数
当这些参数属于一个 POJO 时,我们直接传递 POJO,直接通过#{参数名}
取出传入 pojo 的字段。
4.5. Map
我们也可以直接传入 map,dao 接口的方法为:public User getUserByMap(Map<String,Object> map);
,此时直接通过 #{key}
来取出 map 对应的值。
sql 映射文件为
<select id="getUserByMap" resultType="user">
SELECT * FROM users where id = #{id} and name =#{name}
</select>
测试代码
@Test
public void testParamByMap() throws IOException {
SqlSession sqlSession = getSqlSessionFactory().openSession(true);
try {
UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("id",7);
map.put("name","test");
System.out.println(mapper.getUserByMap(map));
} finally {
sqlSession.close();
}
}
如果传入参数是 Collection(例如 List、Set)类型或者数组,mybatis 会将传入的参数封装到 map 中。此时 Collection 类型的 key 为 collection,如果是 List 还可以使用 list,而数组的 key 为 array。
4.6. ${}
和 #{}
的区别
将 sql 映射文件修改为:
<select id="getUserByIdAndName" resultType="user">
SELECT * FROM users where id = ${id} and name = #{name}
</select>
此时log4j在控制台打印出:
DEBUG 02-18 14:27:36,381 ==> Preparing: SELECT * FROM users where id = 8 and name = ? (BaseJdbcLogger.java:145)
DEBUG 02-18 14:27:36,434 ==> Parameters: test(String) (BaseJdbcLogger.java:145)
DEBUG 02-18 14:27:36,473 <== Total: 1 (BaseJdbcLogger.java:145)
可以看出 sql 语句为 :SELECT * FROM users where id = 8 and name = ?
。
所以:
#{}
:是以预编译的形式,将参数设置到 sql 语句;可以防止 sql 注入${}
:直接将参数拼装到 sql 语句。
默认情况下,使用#{}
格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值 。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY
,你可以这样来使用:
ORDER BY ${columnName}
这里 MyBatis 不会修改或转义字符串。
5. SELECT
5.1. 返回结果封装为 Map
如何将多条记录封装成 Map<Integer,User>
,而 key 为这条记录的主键,值是封装后的 user 对象。
可以通过 @MapKey("id")
注解告诉 mybatis 封装这个 map 时候使用哪个属性作为键。例如:
@MapKey("id")
public Map<Integer,User> getUsersByNameLikeReturnMap(String name);
此时 sql 映射文件:
<select id="getUsersByNameLikeReturnMap" resultType="user">
SELECT * FROM users where name like #{name}
</select>
测试方法:
@Test
public void testReturnMap() throws IOException {
SqlSession sqlSession = getSqlSessionFactory().openSession(true);
try {
UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
Map<Integer, User> map = mapper.getUsersByNameLikeReturnMap("%test%");
System.out.println(map);
} finally {
sqlSession.close();
}
}
5.2. ResultMap
自动映射有三种方法:
1.通过 autoMappingBehavior 开启自动映射的功能,其默认是PARTIAL。唯一的要求是列名和 javaBean 属性名一致。当 autoMappingBehavior 设置为 null 则会取消自动映射。
2.如果数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN -> aColumn,可以通过设置 mapUnderscoreToCamelCase 为 true 来开启自动驼峰命名规则映射。
3.通过自定义 resultMap ,实现高级结果集映射。
resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事 情。 事实上, 编写相似于对复杂语句联合映射这些等同的代码, 也许可以跨过上千行的代码。 ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们 的关系。
ResultMap 标签的概念视图
constructor
: 类在实例化时,用来注入结果到构造方法中
idArg
:ID 参数;标记结果作为 ID 可以帮助提高整体效能arg
: 注入到构造方法的一个普通结果
id
:一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能result
:注入到字段或 JavaBean 属性的普通结果association
:一个复杂的类型关联;许多结果将包成这种类型
- 嵌入结果映射 :结果映射自身的关联,或者参考一个
collection
:复杂类型的集合
- 嵌入结果映射 :结果映射自身的集,或者参考一个
discriminator
:使用结果值来决定使用哪个结果映射
case
:基于某些值的结果映射- 嵌入结果映射 :这种情形结果也映射它本身,因此可以包含很多相 同的元素,或者它可以参照一个外部的结果映射。
ResultMap 的属性
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个result map |
type | 类的全限定名, 或者一个类型别名 |
autoMapping | 如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。 |
例子
dao 接口
public interface UserMapper {
public User getUserById(Integer id);
}
配置 sql 映射文件
<?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.chen.dao.UserMapper">
<resultMap id="UserResultMap" type="user">
<!-- id 指定主键-->
<!-- column 指定列名,property 指定对应 pojo 的属性-->
<id column="id" property="id"/>
<!-- result 指定其他列-->
<result column="name" property="name"/>
<result column="age" property="age"/>
</resultMap>
<select id="getUserById" resultMap="UserResultMap">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
测试方法
@Test
public void testResultMap() throws IOException {
SqlSession sqlSession = getSqlSessionFactory().openSession(true);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(7);
System.out.println(user);
} finally {
sqlSession.close();
}
}
关联查询
在修改完数据库后,对于 user 类关联 department 类。
user 类
public class User {
private int id;
private String name;
private int age;
private Department department;
//get,set方法
}
department 类
public class Department {
private Integer id;
private String name;
//get,set方法
}
在原有的 dao 接口上增加
public User getUserAndDeptById(Integer id);
1.联合查询,使用级联属性封装结果集
此时 sql 映射文件添加
<resultMap id="MyResultMap" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="did" property="department.id"/>
<result column="department_name" property="department.name"/>
</resultMap>
<select id="getUserAndDeptById" resultMap="MyResultMap">
SELECT u.id id,
u.name name,
u.age age,
u.d_id d_id,
d.id did,
d.department_name department_name
FROM users u,department d
WHERE u.d_id=d.id AND u.id=#{id}
</select>
测试方法
@Test
public void testResultMap() throws IOException {
SqlSession sqlSession = getSqlSessionFactory().openSession(true);
try {
UserResultMapMapper mapper = sqlSession.getMapper(UserResultMapMapper.class);
User user = mapper.getUserAndDeptById(7);
System.out.println(user);
} finally {
sqlSession.close();
}
}
2.association 元素来实现多表查询
通过 association 元素来定义关联的单个对象的规则。
sql 映射文件添加
<resultMap id="AssociationResultMap" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!-- association 可以指定关联的 javaBean 对象-->
<!-- property 指定哪个属性是关联的对象-->
<!-- javaType 指定这个属性对象的类型-->
<association property="department" javaType="com.chen.entity.Department">
<id column="did" property="id"/>
<result column="department_name" property="name"/>
</association>
</resultMap>
3.association 元素进行分步查询
还可以通过 association 元素的 select 属性进行分步查询,select 属性是另外一个映射语句的 ID。并且将会通过 column 属性来传递类名。
新建 DepartmentMapper 接口
public interface DepartmentMapper {
public Department getDepartmentById(Integer id);
}
新建 DepartmentMapper.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.chen.dao.DepartmentMapper">
<select id="getDepartmentById" resultType="com.chen.entity.Department">
SELECT id,department_name name FROM department WHERE id=#{id}
</select>
</mapper>
sql 映射文件添加
<resultMap id="AssociationResultMap2" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<association property="department"
select="com.chen.dao.DepartmentMapper.getDepartmentById"
column="d_id"
/>
</resultMap>
<select id="getUserAndDeptByIdStep" resultMap="AssociationResultMap2">
SELECT * FROM users where id = #{id}
</select>
测试代码
@Test
public void testAssociation() throws IOException {
SqlSession sqlSession = getSqlSessionFactory().openSession(true);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserAndDeptByIdStep(7);
System.out.println(user);
} finally {
sqlSession.close();
}
}
4.延迟加载
用分步查询在每次查询 user 对象时,都会同时查询其 department 级联属性。而通过 延迟加载 可以使我们在使用 department 级联属性再去查询。
延迟加载可以通过在 mybatis-config.xml 中的 settings 元素中的 lazyLoadingEnabled 子元素和 aggressiveLazyLoading 子元素来控制。
1.lazyLoadingEnabled
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
fetchType
属性来覆盖该项的开关状态。其默认值为 false 。2.aggressiveLazyLoading
当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考
lazyLoadTriggerMethods
)。其默认值为 false (true in ≤3.4.1)。
在 mybatis3.4.1 版本之前 aggressiveLazyLoading 属性值为 true,所以我们需要显式地指定配置的值。
mybatis-config.xml 中增加配置
<settings>
<setting name="lazyLoadingEnabled " value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
当我没有使用 user 的 department 属性时,mybatis 并没有去查询 department 属性,日志如下
DEBUG 02-22 15:23:38,703 ==> Preparing: SELECT * FROM users where id = ? (BaseJdbcLogger.java:145)
DEBUG 02-22 15:23:38,752 ==> Parameters: 7(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-22 15:23:38,880 <== Total: 1 (BaseJdbcLogger.java:145)
而当我使用时,mybatis 就会自动去查询该属性,日志如下
DEBUG 02-22 15:19:57,262 ==> Preparing: SELECT * FROM users where id = ? (BaseJdbcLogger.java:145)
DEBUG 02-22 15:19:57,309 ==> Parameters: 7(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-22 15:19:57,423 <== Total: 1 (BaseJdbcLogger.java:145)
DEBUG 02-22 15:19:57,424 ==> Preparing: SELECT id,department_name name FROM department WHERE id=? (BaseJdbcLogger.java:145)
DEBUG 02-22 15:19:57,424 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-22 15:19:57,437 <== Total: 1 (BaseJdbcLogger.java:145)
5.collection 定义关联集合封装规则
一个 user 只属于一个department ,而一个 department 可以有很多 user ,我们可以通过 collection 元素来映射嵌套结果集合到 List 中。
Department 类增加一个字段
private List<User> userList;
增加 DepartmentMapper 接口方法
public Department getDepartmentAndUsersById(Integer id);
修改 DepartmentMapper.xml
<resultMap id="deptAndUser" type="com.chen.entity.Department">
<id column="did" property="id"/>
<result column="department_name" property="name"/>
<!-- collection定义集合类型的属性封装规则-->
<collection property="userList" ofType="com.chen.entity.User">
<id column="uid" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
</collection>
</resultMap>
<select id="getDepartmentAndUsersById" resultMap="deptAndUser">
SELECT d.id did,
d.department_name department_name,
u.id uid,
u.name name,
u.age age
FROM department d
LEFT JOIN users u
ON d.id=u.d_id
WHERE d.id=#{id}
</select>
6.collection 分步查询和延迟加载
增加 DepartmentMapper 接口方法
public Department getDepartmentAndUsersByIdStep(Integer id);
增加 UserMapper 接口方法
public List<User> getUsersByDeptId(Integer id);
修改 UserMapper.xml
<!--查询属于该 department 的 user-->
<select id="getUsersByDeptId" resultType="com.chen.entity.User">
SELECT * FROM users where d_id = #{id}
</select>
修改 DepartmentMapper.xml
xml
<!--collection分步查询-->
<select id="getDepartmentAndUsersByIdStep" resultMap="deptAndUserStep">
SELECT id,department_name name FROM department WHERE id=#{id}
</select>
<resultMap id="deptAndUserStep" type="com.chen.entity.Department">
<id column="id" property="id"/>
<result column="department_name" property="name"/>
<collection property="userList" ofType="com.chen.entity.User" select="com.chen.dao.UserMapper.getUsersByDeptId" column="id"/>
</resultMap>
与 association 元素的延迟加载类似,配置 mybatis-config.xml 中的 settings 元素中的 lazyLoadingEnabled 子元素和 aggressiveLazyLoading 子元素即可。
7.分步查询传递多列值和延迟加载的fetchType
在分步查询中,如果要传递多列的值,可以将其封装成 map 后传递,此时 column="{key1=column1,key2=column2}"
。
在 association 和 collection 中,有 fetchType
这个属性,有效值为 lazy
和 eager
,其默认为 lazy
,即延迟加载,如果使用eager
,它将取代全局配置参数lazyLoadingEnabled
。
discriminator 鉴别器
有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理解,因为它的表现很像 Java 语言中的 switch 语句。
鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型(尽管字符串在很多情形下都会有用)。
例子:
如果 user 的年龄等于 20,就查询部门信息,而如果不等于 20,则不查询。
增加 UserMapper 接口方法
public User getUsersByDiscriminator(Integer id);
修改 UserMapper.xml 如下
<resultMap id="usersByDiscriminator" type="com.chen.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!-- column 指定判断哪列-->
<discriminator javaType="int" column="age">
<case value="20" resultType="com.chen.entity.User">
<association property="department"
select="com.chen.dao.DepartmentMapper.getDepartmentById"
column="d_id"
/>
</case>
</discriminator>
</resultMap>
<select id="getUsersByDiscriminator" resultMap="usersByDiscriminator">
SELECT * FROM users where id = #{id}
</select>