映射器是Mybatis中最复杂并且是最重要的组件。它由一个接口和xml映射文件(或者注解)组成。在映射器中我们可以配置各类SQL、动态SQL、缓存、存储过程、级联等复杂的内容。并且通过简易的映射规则映射到指定的POJO或者其它对象上,映射器能有效的消除JDBC的底层代码。在Mybatis的应用程序开发中,映射器的开发工作量占全部工作量的80%,可见其重要性。
映射文件的作用是用来配置SQL映射语句,根据不同的SQL语句性质,使用不同的标签,其中常用的标签有:<select>、<insert>、<update>、<delete>。下面列出了SQL 映射文件的几个顶级元素(按照应被定义的顺序列出):
所以下面我们一起来学习它们。
select元素表示 SQL 的 select 语句,用于查询,而查询语句是我们日常中用的最多的,使用的多就意味它有着强大和复杂的功能,所以我们先来看看select元素的属性有哪些(红色标注为最常用的)。
看到有这么多的属性是不是有点害怕,但是在实际工作中用的最多的是id、parameterType、resultType、resultMap这四个。如果还要设置设置缓存的话,还会使用到flushCache和useCache,而其它的属性是不常用功能,反正我到现在还没有用过其它的。所以我们暂时熟练掌握id、parameterType、resultType、resultMap以及它们的映射规则就行,而flushCache和useCache会在后面的缓存部分进行介绍。
下面使用select元素来举一个例子,这个例子我们前面看到过,就是根据用户Id来查找用户的信息,代码如下:
<!-- 通过Id查询一个用户 --> <select id="selectUserById" parameterType="int" resultType="com.thr.User"> select * from t_user where id = #{id}; </select>
这条SQL语句非常的简单,但是现在的目的是为了举一个例子,看看在实际开发中如何使用映射文件,这个例子只是让我们认识select元素的常用属性及用法,而在以后的开发中我们所遇到的问题要比这条SQL复杂得多,可能有十几行甚至更长。
注意:没有设置的属性全都采用默认值,你不配置不代表这个属性没有用到。
insert元素表示插入数据,它可以配置的属性如下。
下面是insert元素的简单应用,在执行完SQL语句后,会返回一个整数来表示其影响的记录数。代码如下:
<!-- 添加用户--> <insert id="insertUser" parameterType="com.thr.User"> insert into t_user(username, age) values (#{username},#{age}); </insert>
3.1、主键回填
在insert元素中,有一个非常重要且常用的属性——useGeneratedKeys,它的作用的主键回填,就是将当前插入数据的主键返回。例如上面的插入语句中,我们并没有插入主键 Id 列,因为在mysql数据库中将它设置为自增主键,数据库会自动为其生成对应的主键,所以没必要插入这个列。但是有些时候我们还需要继续使用这个主键,用以关联其它业务,所以十分有必要获取它。比如在新增用户的时候,首先会插入用户的数据,然后插入用户和角色关系表,而插入用户时如果没办法取到用户的主键,那么就没有办法插入用户和角色关系表了,因此这个时候需要拿到对应的主键,以方便关联表的操作。
在JDBC中,使用Statement对象执行插入的SQL语句后,可以通过getGeneratedKeys方法来获取数据库生成的主键。而在insert元素中也设置了一个对应的属性useGeneratedKeys,它的默认值为false。当我们把这个属性设置为true时,还需要配置keyProperty或keyColumn(它二者不能同时使用),告诉系统把生成的主键放入哪个属性中,如果存在多个主键,就要用逗号隔开。
我们将上面xml配置文件中的insert语句进行更改,更改后代码如下:
<!-- 添加用户--> <insert id="insertUser" parameterType="com.thr.User" useGeneratedKeys="true" keyProperty="id"> insert into t_user(username, age) values (#{username},#{age}); </insert>
useGeneratedKeys设置为true表示将会采用jdbc的Statement对象的getGeneratedKeys方法返回主键,因为Mybatis的底层始终是jdbc的代码。设置keyProperty对于 id 表示用这个pojo对象的属性去匹配这个主键,它会将数据库返回的主键值赋值为这个pojo对象的属性。测试代码如下:
//添加一个用户数据 @Test public void testInsertUser(){ String statement = "com.thr.User.insertUser"; User user = new User(); user.setUsername("黄飞鸿"); user.setAge(30); sqlSession.insert(statement, user); //提交插入的数据 sqlSession.commit(); sqlSession.close(); //输出返回的主键只 System.out.println("插入的主键值为:"+user.getId()); }
输出结果为:
通过结果可以发现我们已经获取到插入数据的主键了。
3.2、自定义主键
自定义主键,顾名思义就是我们自己定义返回的主键值。有时候我们的不想按照数据库自增的规则,例如每次插入主键+2,又或者随机生成数据。那么Mybatis对于这样的场景也提供了支持,它主要依赖于selectKey元素进行支持,它允许自定义键值的生成规则,如下代码:
<!-- 添加用户--> <insert id="insertUser" parameterType="com.thr.User"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select ROUND(RAND()*1000) </selectKey> insert into t_user(id,username, age) values (#{id},#{username},#{age}); </insert>
执行的流程是:首先通过 select ROUND(RAND()*1000)得到主键值,然后将得到的值设置到 user 对象的 id 中,再最后进行 insert 操作。
下面再来介绍一下相关的标签:
测试运行结果为:
update元素和delete元素在使用上比较简单,所以这里把它们放在一起论述。它们和insert元素的属性差不多,执行完后也会返回一个整数,用来表示该SQL语句影响了数据库的记录行数。它们二者的使用代码如下所示:
<!-- 根据Id更新用户 --> <update id="updateUser" parameterType="com.thr.User"> update t_user set username = #{username},age = #{age} where id = #{id} </update> <!-- 根据Id删除用户 --> <delete id="deleteUser" parameterType="int"> delete from t_user where id = #{id} </delete>
由于在使用上比较简单,所以就不做多介绍了,具体可以参考前面select和insert元素。
sql元素是用来定义可重用的 sql代码片段,这样在字段比较多的时候,以便在其它语句中使用。
<!--定义sql代码片段--> <sql id="userCols"> id,username,age </sql> <!-- 查询所有用户 --> <select id="selectAllUser" resultType="com.thr.User"> select <include refid="userCols"/> from t_user </select> <!-- 添加用户--> <insert id="insertUser" parameterType="com.thr.User"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select ROUND(RAND()*1000) </selectKey> insert into t_user(<include refid="userCols"/>) values (#{id},#{username},#{age}); </insert>
sql元素还支持变量的传递,代码如下。
<!--定义sql代码片段--> <sql id="userCols"> ${alias}.id,${alias}.username,${alias}.age </sql> <!-- 查询所有用户 --> <select id="selectAllUser" resultType="com.thr.User"> select <include refid="userCols"> <property name="alias" value="u"/> </include> from t_user u </select>
在include元素中定义了一个命名为alias的变量,其值是表t_user的别名u,然后sql元素就能自动识别到对于表的变量名,例如u.id、u.username、u.age。这种方式对于多表查询很有用,但也用的不多。
6.1、映射基本数据类型(即八大基本数据类型,比如int,boolean,long等类型)
根据id查询一个用户:selectUserById,那么传入的就应该是int类型的值。所以使用别名int来映射传入的值。
<!-- 通过Id查询一个用户 --> <select id="selectUserById" parameterType="int" resultType="com.thr.User"> select * from t_user where id = #{id}; </select>
6.2、映射pojo类型(即普通的对象,比如user的javabean对象)
添加用户:insertUser。这里传入的就是一个pojo类型。
<!-- 添加用户--> <insert id="insertUser" parameterType="com.thr.User"> insert into t_user(id,username,age) values (#{id},#{username},#{age}); </insert>
6.3、包装pojo类型(即内部属性为对象引用,集合等)
那什么是包装pojo类型呢?比如如下的代码:
public class QueryVo { //有个对象引用,可能是普通的pojo,也有可能是集合 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
根据用户名和年龄查询用户信息:selectUserByUserNameAndAge。传入一个包装pojo类型,其内部有个属性是user的引用。
<!-- 通过username和age查询一个用户 --> <select id="selectUserByUserNameAndAge" parameterType="com.thr.QueryVo" resultType="com.thr.User"> select * from t_user where username = #{user.username} and age = #{user.age}; </select>
测试代码:
@Test public void testSelectUserByUserNameAndAge(){ String statement = "com.thr.User.selectUserByUserNameAndAge"; QueryVo vo = new QueryVo(); User user = new User(); user.setUsername("马保国"); user.setAge(30); vo.setUser(user); List<User> listUser = sqlSession.selectList(statement, vo); for(User u : listUser){ System.out.println(u); } sqlSession.close(); }
注意:user.username这个属性的获取,因为QueryVO是一个包装pojo,其中有user的引用。而user中又有username的属性,那么这样一层层取过来用即可。
6.4、映射map集合
这个也很简单,理解了前面的,这个不难。就是通过map集合设置key和value的值,然后在映射文件中获取对应的key即可 #{key}。
<!-- 通过username和age查询一个用户 --> <select id="selectUserByMap" parameterType="hashmap" resultType="com.thr.User"> select * from t_user where username = #{username} and age = #{age}; </select>
测试代码:
@Test public void testSelectUserByMap(){ String statement = "com.thr.User.selectUserByMap"; //创建HashMap对象 HashMap<String, Object> map = new HashMap<>(); //put值 map.put("username","马保国"); map.put("age",30); List<User> listUser = sqlSession.selectList(statement, map); for(User u : listUser){ System.out.println(u); } sqlSession.close(); }
注意:这里的hashmap使用的是别名,mybatis中内置了。
resultType为输出结果集类型,同样支持基本数据类型、pojo类型及map集合类型。SQL语句查询后返回的结果集会映射到配置标签的输出映射属性对应的Java类型上。
输出映射有两种配置,分别是resultType和resultMap,注意两者不能同时使用。
7.1、映射基本数据类型
<!-- 统计用户总数量 --> <select id="countUsers" resultType="int"> select count(1) from t_user </select>
7.2、映射pojo对象
<!-- 通过Id查询一个用户,resultType配置为PoJo类型 --> <select id="selectUserById" parameterType="int" resultType="com.thr.User"> select * from t_user where id = #{id}; </select>
7.3、映射pojo列表(映射多列数据)
映射单个pojo对象和映射pojo列表映射文件中的resultType都配置为pojo类型。区别只是返回单个对象是内部调用selectOne返回pojo对象,返回pojo列表时内部调用selectList方法。
<!-- 查询所有用户 --> <select id="selectAllUser" resultType="com.thr.User"> select * from t_user </select>
7.4、映射hashmap
<!-- 查询所有用户, resultType为hashmap--> <select id="selectAllUser" resultType="hashmap"> select * from t_user </select>
测试代码:
//查询所有用户数据,通过HashMap @Test public void testSelectAllUser(){ String statement = "com.thr.User.selectAllUser"; List<HashMap<String, Object>> listUser = sqlSession.selectList(statement); for (HashMap<String, Object> map: listUser) { Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<String, Object> entry = iterator.next(); System.out.println("key= "+entry.getKey()+" and value= "+entry.getValue()); } } sqlSession.close(); }
我们在使用resultType的时候,前提是数据库表中的字段名和表对应实体类的属性名称一致才行(包括驼峰原则),但是在平时的开发中,表中的字段名和表对应实体类的属性名称往往不一定都是完全相同的,这样就会导致数据映射不成功,从而查询不到数据。那为了解决这个问题,我需要使用resultMap,通过resultMap将字段名和属性名作一个对应关系。
下面先来简单体验一下resultType的使用,为了让例子更加好,我将数据库表t_user和User实体进行了修改,如下:
向表t_user中添加一些数据:
use user insert into t_user(username,age,birthday,sex,address) values (‘唐浩荣‘,18,‘2020-11-10‘,1,‘上海‘); insert into t_user(username,age,birthday,sex,address) values (‘蔡徐坤‘,18,‘2018-01-18‘,0,‘北京‘); insert into t_user(username,age,birthday,sex,address) values (‘黄飞鸿‘,18,‘2020-11-10‘,1,‘大清‘); insert into t_user(username,age,birthday,sex,address) values (‘十三姨‘,18,‘2020-11-10‘,0,‘大清‘); insert into t_user(username,age,birthday,sex,address) values (‘马保国‘,18,‘2020-11-10‘,0,‘深圳‘);
这种情况我们就可以使用resultMap进行映射。下面配置查询结果的列名和实体类的属性名的对应关系,修改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.thr.User"> <!-- 配置查询结果的列名和实体类的属性名的对应关系 --> <!--id:唯一标识,type:需要映射的java类型--> <resultMap id="userMap" type="com.thr.User"> <!-- 与主键字段的对应,property对应实体属性,column对应表字段 --> <id property="userId" column="id"/> <!-- 与非主键字段的对应,property对应实体属性,column对应表字段 --> <result property="userName" column="username"/> <result property="userAge" column="age"/> <result property="userBirthday" column="birthday"/> <result property="userSex" column="sex"/> <result property="userAddress" column="address"/> </resultMap> <!-- 查询所有用户,返回集为resultMap类型,resultMap的value上面配置的id=userMap要一致--> <select id="selectAllUser" resultMap="userMap"> select * from t_user </select> </mapper>
测试代码:
//查询所有用户数据 @Test public void testSelectAllUser(){ String statement = "com.thr.User.selectAllUser"; List<User> listUser = sqlSession.selectList(statement); for (User user : listUser) { System.out.println(user); } sqlSession.close(); }
运行结果:
当然,还有一种方式可以不用resultMap元素,就是sql 查询取别名时与pojo属性一致即可,但是不推荐,这样sql的可读性差),举例代码如下。
<select id="selectAllUser" resultMap="userMap"> select id userId,username userName,age userAge,birthday userBirthday,sex userSex,address userAddress from t_user </select>
8.1、resultMap元素介绍
额外,resultMap还有高级映射功能,还可以实现将查询结果映射为复杂类型的pojo类型,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询,这个会在后面单独进行详细的介绍,因为这个点非常非常非常重要,所以这里不多说。我们下面来详细介绍一下resultMap元素。
<resultMap id="" type="" extends="" autoMapping=""> <constructor><!--构造器注入属性值--> <idArg/> <arg/> </constructor> <id/><!--主键的映射规则--> <result/><!--非主键的映射规则--> <association/><!--高级映射--> <collection /><!--高级映射--> <discriminator> <case/> </discriminator><!--根据返回的字段的值封装不同的类型--> </resultMap>
①、resultMap元素包含的属性:
②、<constructor>
使用构造方法映射属性值,你可能会问既然通过<id>和<result>就可以给属性注入值了,那为什么还要用构造方法映射值,原因是万一JavaBean中有个这样的构造方法:
这样我们就可以通过构造方法来映射值了。只是我们用的非常少罢了。
<resultMap id="userConstructorMap" type="com.thr.User"> <constructor> <idArg column="id" name="userId" javaType="int"/> <arg column="username" name="userName" javaType="string"/> <arg column="age" name="userAge" javaType="int"/> </constructor> </resultMap>
③、<id>和<result>
它们二者的内部属性一致,如下:
④、<association>、<collection>和<discriminator>
这些元素都是关于级联的问题比较复杂,所以这里就不探讨了,后面会介绍到。
原文:https://www.cnblogs.com/tanghaorong/p/13943518.html