Mybatis教程 | 第三篇:深入Mapper映射文件

本文详细介绍了MyBatis的Mapper映射文件,包括select、insert、update、delete等标签的使用,以及sql标签和resultMap的配置。通过示例展示了如何在映射文件中进行参数映射和结果映射,强调了resultMap在处理复杂查询结果中的重要性。此外,文章还提到了使用SqlSession的改进方法,推荐通过mapper接口的代理对象访问MyBatis。
摘要由CSDN通过智能技术生成

前言

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL映射文件常用的元素如下:
cache – 给定命名空间的缓存配置;
cache-ref – 其他命名空间缓存配置的引用;
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象;
sql – 可被其他语句引用的可重用语句块;
insert – 映射插入语句;
update – 映射更新语句;
delete – 映射删除语句;select – 映射查询语句;
下一部分将从语句本身开始来描述每个元素的细节。

测试select标签

select标签用来映射查询语句,它是Mybatis常用标签之一。
执行简单查询的select标签是非常简单的,例如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号#{id},这就告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

当然,这需要很多单独的 JDBC 的代码来提取结果并将它们映射到对象实例中,这就是 MyBatis 节省你时间的地方。我们需要深入了解参数和结果映射,细节部分我们下面来了解。
select 元素有很多属性允许你配置,来决定每条语句的作用细节,如下所示:

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

select标签的属性描述如下:

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句,在dao层接口中对应的就是方法名,要确保唯一性,也就是dao层接口不允许重载
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unse
resultType从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用
resultMap外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用
flushCache将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false
useCache将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)
fetchSize这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)
databaseId如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略
resultOrdered这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
resultSets这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的

下面是一个简单的查询示例:
配置文件沿用第一篇中的配置文件
数据库表结构
在这里插入图片描述
持久化对象:

public class User{
	
	private Integer userId;
	
	private String userName;
	
	private String userGender;
	
	private Integer userAge;

	public Integer getUserId() {
		return userId;
	}

	public void setUserId(Integer userId) {
		this.userId = userId;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getUserGender() {
		return userGender;
	}

	public void setUserGender(String userGender) {
		this.userGender = userGender;
	}

	public Integer getUserAge() {
		return userAge;
	}

	public void setUserAge(Integer userAge) {
		this.userAge = userAge;
	}

	public User() {
		super();
	}

	public User(Integer userId, String userName, String userGender, Integer userAge) {
		super();
		this.userId = userId;
		this.userName = userName;
		this.userGender = userGender;
		this.userAge = userAge;
	}

	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userGender=" + userGender + ", userAge="
				+ userAge + "]";
	}
}

mapper文件

<?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.zepal.mybatis.mapper.UserMapper">
  
  <resultMap type="com.zepal.mybatis.domain.User" id="userMap">
  	<id column="user_id" property="userId"/>
  	<result column="user_name" property="userName"/>
  	<result column="user_gender" property="userGender"/>
  	<result column="user_age" property="userAge"/>
  </resultMap>
  <select id="getUserById" parameterType="int" resultMap="userMap">
  	SELECT * from tb_user WHERE user_id = #{userId};
  </select>
  
</mapper>

测试代码

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = sqlSession.selectOne("com.zepal.mybatis.mapper.UserMapper.getUserById", 1);
		System.out.println(user.toString());
		sqlSession.close();
	}
	
}

运行结果

DEBUG [main] - ==>  Preparing: SELECT * from tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [userId=1, userName=孙悟空, userGender=男, userAge=1000]

说明
对照上面select标签属性表,id是命名空间的唯一标识符,parameterType指明该查询语句允许接收一个int类型的参数(其它的标签属性这里暂时未用到,但一定要熟悉于胸,后面会用到)。resultMap属性标签引用了外部resultMap标签。这里解决的问题就是:由于在数据库中的表字段user_name,而持久层对象是userName,导致mybatis对结果集处理的时候,无法构造对象的属性,就需要结果集映射。
resultMap标签常用属性:
id–resultMap的唯一标识符,其它标签需要引用当前结果集映射的时候,通过id引入。
type–实际返回类型,告诉Mybatis需要去找哪个持久层对象进行结果集映射,这里一般需要全路径,虽然mybatis提供了typeAliases进行别名处理,但是为了方便排错,建议全路径。
autoMapping–如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior。默认值为:unset
:在实际开发中,有很多复杂情况,比如多表联合查询,这时简单的结果集映射已经无法满足了,就需要使用resultMap的关联属性来处理,后续的博文会继续深入resultMap。
在select标签的属性中,对结果的处理,除了resultMap以外,还有一个resultType(resultMap和resultType不能同时使用),从字面意思理解就是结果类型,允许接收类的完全限定名或别名(就是说没有被typeAliases处理过的,就需要全类名)。如果指定的返回结果类型是持久层对象,按照resultMap,假如说持久层对象和数据库字段,属性名不一致,mybatis可以构造返回结果对象,但是属性为null。这里除了指定持久层对象,mybatis还允许Map作为返回类型,因为Map是Mybatis中typeAliases默认处理的,所以指定Map的时候不用全类名(比如java.util.Map),直接使用map即可,返回结果使用列名作为key,列值作为value。如下

resultType="map"

使用map运行结果为下

DEBUG [main] - ==>  Preparing: SELECT * from tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
{user_id=1, user_name=孙悟空, user_age=1000, user_gender=男}

这里有个短板就是map集合不能很好描述一个领域对象,所以一般强制使用POJO去描述数据库表。所以使用resultMap比较多。
针对多条查询结果,map就需要用类似如下的数据结构:

List<Map<String, Object>>

而SqlSession调用的方法就应该是sqlSession.selectList().

insert标签测试

执行简单的新增功能标签如下

<insert id="saveUser" parameterType="com.zepal.mybatis.domain.User" useGeneratedKeys="true">
  	INSERT INTO tb_user (user_name, user_gender, user_age)
	VALUES
	(#{userName}, #{userGender}, #{userAge});
  </insert>

insert标签常用属性描述如下:

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句,在dao层接口中对应的就是方法名,要确保唯一性,也就是dao层接口不允许重载
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unse
flushCache将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false
keyProperty(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
databaseId如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略

下面是一个简单insert示例:
配置文件、持久化对象和数据库表结构同上。
mapper文件

<insert id="saveUser" parameterType="com.zepal.mybatis.domain.User" useGeneratedKeys="true">
  	INSERT INTO tb_user (user_name, user_gender, user_age)
	VALUES
	(#{userName}, #{userGender}, #{userAge});
  </insert>

测试代码

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = new User();
		user.setUserName("唐三藏");
		user.setUserGender("男");
		user.setUserAge(5000);
		int result = sqlSession.insert("com.zepal.mybatis.mapper.UserMapper.saveUser", user);
		System.out.println("返回受影响的行 : " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}

运行结果:

DEBUG [main] - ==>  Preparing: INSERT INTO tb_user (user_name, user_gender, user_age) VALUES (?, ?, ?); 
DEBUG [main] - ==> Parameters: 唐三藏(String), 男(String), 5000(Integer)
DEBUG [main] - <==    Updates: 1
返回受影响的行 : 1

说明:
对照上面insert标签属性表,id是命名空间的唯一标识符,parameterType指明该查询语句允许接收一个User类型的参数(其它的标签属性这里暂时未用到,但一定要熟悉于胸,后面会用到)。useGeneratedKeys表示使用自增策略(需要数据库支持,可以在mybatis配置文件打开全局开关),这里不用指明返回结果类型insert标签页没有这项属性,mybatis会根据JDBC自动返回受影响的行(只针对DML语言有限,即新增、修改和删除)。
那么有这么一个场景,我需要返回当前插入结果中的数据。mybatis也是提供了支持,就是用insert属性表描述中的keyProperty和keyColumn,需要useGeneratedKeys设置为true的支持。
mapper文件:

<insert keyProperty="userId" keyColumn="user_id" id="saveUser" parameterType="com.zepal.mybatis.domain.User" useGeneratedKeys="true">
  	INSERT INTO tb_user (user_name, user_gender, user_age)
	VALUES
	(#{userName}, #{userGender}, #{userAge});
  </insert>

测试代码

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = new User();
		user.setUserName("黑山老妖");
		user.setUserGender("男");
		user.setUserAge(3000);
		int result = sqlSession.insert("com.zepal.mybatis.mapper.UserMapper.saveUser", user);
		System.out.println("返回受影响的行 : " + result);
		System.out.println(user.getUserId());
		sqlSession.commit();
		sqlSession.close();
	}
}

运行结果

DEBUG [main] - ==>  Preparing: INSERT INTO tb_user (user_name, user_gender, user_age) VALUES (?, ?, ?); 
DEBUG [main] - ==> Parameters: 黑山老妖(String), 男(String), 3000(Integer)
DEBUG [main] - <==    Updates: 1
返回受影响的行 : 1
5

mapper只是多了两项属性keyProperty(持久层对象的属性)和keyColumn(表字段PS:在mybatis中,colum都是指向数据库、property指向持久层对象属性,例如resultMap)。在测试代码中打印userId。但是在新增之前,我并没有添加此属性,说明返回的userId就是当前新增数据生成的userId,并将其封装进传递的user对象中。
注意:keyProperty、keyColumn是支持多字段返回的,只需要用英文的逗号隔开即可,而且只持久层对象作为传递参数,比如说我上面传递的不是user,而是单个的属性(将name、gender、age作为单个参数传递),那么就造成了mybatis对返回的字段无处可放的尴尬地步。对于像oracle这种不支持自增长主键的,就需要用下面的方法来处理:

<insert id="insertAuthor">
	  <selectKey keyProperty="userId" keyColumn="user_id" resultType="int" order="BEFORE">
	    select SEQUENCE_TB_USER.nextval as user_id from tb_user;
	  </selectKey>
	  INSERT INTO tb_user (user_id,user_name, user_gender, user_age)
		VALUES
		(#{userId},#{userName}, #{userGender}, #{userAge});
</insert>

在上面的示例中,selectKey元素将会首先运行,其通过查询SEQUENCE序列,TB_USER的user_id会被设置,然后插入语句会被调用(当然,selectKey标签中还可以运行其他逻辑)。
selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">

属性描述:

属性描述
keyPropertyselectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
resultType结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
order这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。
与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型

测试UPDATE、DELETE

update和delete标签属性几乎和insert属性一样,所以就不一一罗列了。唯一不同的是,useGeneratedKeys、keyColumn、keyProperty对delete是无效的。
友情提示:在开发中,修改和删除记得带上条件,不然该跑路了。
update测试:
mapper文件(条件user_id在开发中当然是传递进来的,这里图方便就直接给了,另外一点就是因为mybatis提供了typeAliases的默认处理,所以string是识别的)

<update id="updateUser" parameterType="string">
  	update   tb_user set user_name = #{userName}  where user_id = 1;
  </update>

测试代码:

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		String userName = "玉皇大帝";
		int result = sqlSession.insert("com.zepal.mybatis.mapper.UserMapper.updateUser", userName);
		System.out.println("返回受影响的行 : " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}

执行结果

DEBUG [main] - ==>  Preparing: update tb_user set user_name = ? where user_id = 1; 
DEBUG [main] - ==> Parameters: 玉皇大帝(String)
DEBUG [main] - <==    Updates: 1
返回受影响的行 : 1

delete测试
mapper文件:

<delete id="deleteUserWithId" parameterType="int">
  	delete from tb_user where user_id = #{userId};
  </delete>

测试代码

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		Integer userId = 5;
		int result = sqlSession.delete("com.zepal.mybatis.mapper.UserMapper.deleteUserWithId", userId);
		System.out.println("返回受影响的行 : " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}

执行结果

DEBUG [main] - ==>  Preparing: delete from tb_user where user_id = ?; 
DEBUG [main] - ==> Parameters: 5(Integer)
DEBUG [main] - <==    Updates: 1
返回受影响的行 : 1

sql标签

这个元素可以被用来定义可重用的 SQL 代码段(即在sql标签中定义一段SQL语句,其它标签就可以引用这段在sql标签中定义的SQL语句),可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:

<delete id="deleteUseriwthId" parameterType="int">
  	delete from tb_user
  	<include refid="deleteSql">
  		<property name="id" value="userId"/>
  	</include>
  </delete>
  <sql id="deleteSql">
  	where user_id = #{id};
  </sql>

解释:delete标签引用了sql标签中的SQL语句(通过sql标签的id属性标识),组合起来就是delete from tb_user where user_id = ?。SqlSession像delete标签传递一个userId参数,通过property标签,将参数命名为id传递给sql标签。

测试代码及运行结果

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		Integer userId = 1;
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		int result = userDao.deleteUseriwthId(userId);
		System.out.println("受影响的行 : " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: delete from tb_user where user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行 : 1

改进

上面所有的示例中,SqlSession都是直接调用自身的方法进行操作数据库的,但是这样不仅不容易维护,而且每次进行CRUD操作都不够灵活(每次指定mapper标签都要全路径)。所以我们需要将其抽离出来。在第一篇中介绍SqlSessiong的时候,在其常用方法表中说明,Mybatis官方手册建议通过mapper接口的代理对象访问mybatis。
项目结构:
在这里插入图片描述
操作步骤:
新增一个代理对象的接口

public interface UserDao {

	int deleteUseriwthId(Integer userId);
}

在mapper文件中引入当前代理对象,即命名空间要以全路径指向dao层接口

<mapper namespace="com.zepal.mybatis.dao.UserDao">

mapper中的标签id指向dao层(即代理对象)接口的方法

<delete id="deleteUseriwthId" parameterType="int">
  	delete from tb_user where user_id = #{userId};
  </delete>

测试代码和结果

public class MybatisTest {
	
	public static void main(String[] args) {
		InputStream is = MybatisTest.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		Integer userId = 4;
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		int result = userDao.deleteUseriwthId(userId);
		System.out.println("受影响的行 : " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}
受影响的行 : 1

此篇完结

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值