MyBatis的真正强大之处在于可以配置SQL映射语句。相比于JDBC,使用MyBatis可使SQL映射配置的代码量大大减少,并且MyBatis专注于SQL语句,对于开发人员来说,进行项目开发时,使用MyBatis的SQL映射配置可最大限度地进行SQL调优,以保证性能。MyBatis通过许多特定的元素将SQL映射语句配置在映射文件中。下面将对MyBatis映射文件及其元素进行详细讲解。
在 MyBatis 映射文件中,<mapper>元素是映射文件的根元素,其他元素都是它的子元素。MyBatis 映射文件中的常用元素如表2-8所示。
表2-8 MyBatis映射文件中的常用元素
在不同的映射文件中,<mapper>元素的子元素的 id 可以是相同的,MyBatis 通过<mapper>元素的namespace属性值和子元素的id区分不同的Mapper.xml文件。接口中的方法与映射文件中SQL语句id应一一对应。
<select>元素用于映射查询语句,它可以从数据库中查询数据并返回。使用<select>元素执行查询操作非常简单,示例代码如下:
<!--查询操作 --> <select id="findUserById" parameterType="Integer" resultType="com.itheima.pojo.User"> select * from users where id = #{id} </select>
上述语句中的<select>元素id属性的值为findUserById;parameterType属性的值为Integer,表示接收一个int(或Integer)类型的参数;resultType的值为User类的全限定类名,表示返回一个User类型的对象;参数符号#{参数名}表示创建一个预处理语句参数,在JDBC中,这个预处理语句参数由“?”来标识。
<select>元素中,除了上述示例代码中的几个属性外,还有其他一些可以配置的属性,具体如表2-9所示。
表2-9 <select>元素的常用属性
<insert>元素用于映射插入语句,在执行完<insert>元素中定义的SQL语句后,会返回插入记录的数量。
使用<insert>元素执行插入操作非常简单,示例代码如下:
<!--插入操作 --> <insert id="addUser" parameterType="com.itheima.pojo.User"> insert into users(uid,uname,uage)values(#{uid},#{uname},#{uage}) </insert>
上述语句中的唯一标识为 addUser;parameterType 属性的值为 User 类的全限定类名,表示接收一个User类型的参数;SQL语句中的#{uid}、#{uname}和#{uage}表示通过占位符的形式接收参数uid、uname和uage。
<insert>元素除了上述示例代码中的几个属性外,还有其他一些可以配置的属性,具体如表2-10所示。
表2-10 <insert>元素的其他常用属性
续表
通常,执行插入操作后需要获取插入成功的数据生成的主键值,不同类型数据库获取主键值的方式不同,下面分别对支持主键自动增长的数据库获取主键值和不支持主键自动增长的数据库获取主键值的方式进行介绍。
1.使用支持主键自动增长的数据库获取主键值
如果使用的数据库支持主键自动增长(如MySQL和SQL Server),那么可以通过keyProperty属性指定POJO类的某个属性接收主键返回值(通常会设置到id属性上),然后将useGeneratedKeys的属性值设置为true。示例代码如下:
<!--数据库支持主键自动增长,插入数据,并返回插入成功的数据生成的主键值 --> <insert id="addUser" parameterType="com.itheima.pojo.User" keyProperty="uid" useGeneratedKeys="true" > insert into users(uid,uname,uage)values(#{uid},#{uname},#{uage}) </insert>
2.使用不支持主键自动增长的数据库获取主键值
如果使用的数据库不支持主键自动增长(如 Oracle),或者支持增长的数据库取消了主键自增的规则,可以使用MyBatis提供的<selectKey>元素来自定义主键。<selectKey>元素在使用时可以设置以下几种属性:
<selectKey keyProperty="id" resultType="Integer" order="BEFORE" statementType="PREPARED">
在上述<selectKey>元素的几个属性中,keyProperty、resultType和statementType的作用与前面讲解的相同,这里不重复介绍。order 属性可以被设置为BEFORE 或AFTER。如果设置为BEFORE,那么它会首先执行<selectKey>元素中的配置来设置主键,然后执行插入语句;如果设置为 AFTER,那么它先执行插入语句,然后再执行<selectKey>元素中的配置内容。
使用MyBatis提供的<selectKey>元素来自定义生成主键的具体配置示例如下:
<!--数据库不支持主键自动增长,插入数据,并返回插入成功的数据生成的主键值 --> <insert id="addUser" parameterType="com.itheima.po.User"> <selectKey keyProperty="uid" resultType="Integer" order="BEFORE"> select if(max(uid) is null, 1, max(uid) +1) as newId from users </selectKey> insert into users(uid,uname,uage)values(#{uid},#{uname},#{uage}) </insert>
执行上述示例代码时,<selectKey>元素会先运行,通过自定义的语句来设置数据表中的主键,然后再调用插入语句。在设置数据表中的主键时,如果users表中没有记录,则将uid设置为1,否则将uid的最大值加1,作为新的主键。
<update>元素用于映射更新语句,它可以更新数据库中的数据。在执行完元素中定义的SQL语句后,会返回更新的记录数量。
使用<update>元素执行更新操作非常简单,示例代码如下:
<!--更新操作 --> <update id="updateUser" parameterType="com.itheima.pojo.User"> update users set uname= #{uname},uage = #{uage} where uid = #{uid} </update>
在上述语句中,<update>元素id属性的值为updateUser;parameterType属性的值为User类的全限定类名,表示接收一个User类型的参数;SQL语句中的#{uid}、#{uname}和#{uage}表示通过占位符的形式接收参数uid、uname和uage。
<update>元素属性与<insert>元素属性一致,这里不再重复介绍。
<delete>元素用于映射删除语句,在执行完<delete>元素中的SQL语句之后,会返回删除的记录数量。
使用<delete>元素执行删除操作非常简单,示例代码如下:
<!-- 删除操作 --> <delete id="deleteUser" parameterType="Integer"> delete from users where uid=#{uid} </delete>
<delete>元素除了上述示例代码中的几个属性外,还有其他一些可以配置的属性,具体如表2-11所示。
表2-11 <delete>元素的常用属性
由表2-11可知,<delete>元素的属性与<insert>、<update>元素的属性大致相同,但<delete>元素比<insert>、<update>元素少了3个与键值相关的属性,即keyProperty、keyColumn和useGeneratedKeys。
在一个映射文件中,通常需要定义多条SQL语句,这些SQL语句的组成可能有一部分是相同的(如多条select语句中都查询相同的id、username字段),如果每一个SQL语句都重写一遍相同的部分,势必会增加代码量,导致映射文件过于臃肿。针对以上问题,可以在映射文件中使用MyBatis所提供的<sql>元素,将这些SQL语句中相同的组成部分抽取出来,然后在需要的地方引用。
<sql>元素的作用是定义可重用的SQL代码片段,它可以被包含在其他语句中。<sql>元素可以被静态地(在加载参数时)参数化,<sql>元素不同的属性值随包含的对象不同而发生变化。例如,定义一个包含uid、uname和uage字段的代码片段,具体如下:
<sql id="userColumns">${alias}.uid,${alias}.uname,${alias}.uage</sql>
这一代码片段可以包含在其他语句中使用,如select查询语句、update更新语句等。下面将上述代码片段包含在select查询语句中,具体如下:
<select id="findUserById" parameterType="Integer"
resultType="com.itheima.pojo.User">
select <include refid="userColumns"/>
<property name="alias" value="t1" >
from users
where uid = #{uid}
</select>
在上述代码中,使用<include>元素的refid属性引用了自定义的代码片段,refid属性的值为自定义代码片段的id。使用<property>元素将<sql>元素参数化,<sql>元素中的属性值随包含的对象而发生变化。
上面示例只是一个简单的引用查询,在实际开发中,可以更灵活地定义 SQL 片段。下面实现一个根据客户id查询客户信息的SQL片段,示例代码如下:
1 <!--定义要查询的表 --> 2 <sql id="someinclude"> 3 from 4 <include refid="${include_target}" /> 5 </sql> 6 <!--定义查询列 --> 7 <sql id="userColumns"> 8 uid,uname,uage 9 </sql> 10 <!--根据客户id查询客户信息 --> 11 <select id="findUserById" parameterType="Integer" 12 resultType="com.itheima.pojo.User"> 13 select 14 <include refid="userColumns"/> 15 <include refid="someinclude"/> 16 <property name="include_target" value="users" /> 17 </include> 18 where uid = #{uid} 19 </select>
上述代码中,第2~5行代码定义了需要查询的表,代码片段中的“${include_target}”会获取name为include_target的<property>的值,由于值为users,所以最后要查询的表为“users”;第7~9行代码定义了需要查询的列,代码片段中定义了需要查询的列为uid、uname、和uage;第11~19行代码定义了一个<select>查询元素,根据客户的uid查询客户信息。其中,第14行和第15行代码分别引用了id为userColumns 和id为someinclude的<sql>代码片段。所有的<sql>代码片段在程序运行时都会由MyBatis组合成SQL语句来执行需要的操作。
<resultMap>元素表示结果映射集,是MyBatis中最重要也是功能最强大的元素。<resultMap>元素的主要作用是定义映射规则、更新级联和定义类型转化器等。
默认情况下,MyBatis 程序在运行时会自动将查询到的数据与需要返回的对象的属性进行匹配赋值(数据表中的列名与对象的属性名称完全一致才能匹配成功并赋值)。然而实际开发时,数据表中的列和需要返回的对象的属性可能不会完全一致,这种情况下MyBatis不会自动赋值,这时就需要使用<resultMap>元素进行结果集映射。
下面通过一个具体的案例演示使用<resultMap>元素进行结果集映射,具体步骤如下。
(1)在名称为mybatis的数据库中,创建一个t_student表,并插入几条测试数据,具体代码如下:
USE mybatis; CREATE TABLE t_student( sid INT PRIMARY KEY AUTO_INCREMENT, sname VARCHAR(50), sage INT ); INSERT INTO t_student(sname,sage) VALUES('Lucy',25); INSERT INTO t_student(sname,sage) VALUES('Lili',20); INSERT INTO t_student(sname,sage) VALUES('Jim',20);
(2)在项目src/main/java目录下创建com.itheima.pojo包,并在com.itheima.pojo包中创建实体类Student,用于封装学生信息。在类中定义id、name和age属性,以及属性的getter/setter方法和toString()方法。Student类的具体实现代码如文件2-1所示。
1 package com.itheima.pojo; 2 public class Student { 3 private Integer id; //学生id 4 private String name; //学生姓名 5 private Integer age; //学生年龄 6 public Integer getId() { 7 return id; 8 } 9 public void setId(Integer id) { 10 this.id = id; 11 } 12 public String getName() { 13 return name; 14 } 15 public void setName(String name) { 16 this.name = name; 17 } 18 public Integer getAge() { 19 return age; 20 } 21 public void setAge(Integer age) { 22 this.age = age; 23 } 24 @Override 25 public String toString() { 26 return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; 27 } 28 }
(3)在项目的src/main/java目录下创建com.itheima.mapper包,并在com.itheima.mapper包中创建映射文件StudentMapper.xml,并在映射文件中编写映射查询语句。StudentMapper.xml映射文件主要用于实现SQL语句和Java对象之间的映射,使SQL语句查询出来的关系型数据能够被封装成Java对象。StudentMapper.xml具体代码如文件2-2所示。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="com.itheima.mapper.StudentMapper"> 6 <resultMap type="com.itheima.pojo.Student" id="studentMap"> 7 <id property="id" column="sid"/> 8 <result property="name" column="sname"/> 9 <result property="age" column="sage"/> 10 </resultMap> 11 <select id="findAllStudent" resultMap="studentMap"> 12 select * from t_student 13 </select> 14 </mapper>
在文件2-2中,第6~10行代码使用<resultMap>元素进行结果集的映射。其中,<resultMap>的子元素<id>和子元素<result>的property属性的值与Student类的属性名一一对应,column属性的值与数据表t_student的列名一一对应;第11~13行代码定义了一个查询映射。<select>元素的resultMap属性的值为studentMap,表示引用id值为studentMap 的<resultMap>元素。
(4)在核心配置文件mybatis-config.xml中,引入StudentMapper.xml,将StudentMapper.xml映射文件加载到程序中。在mybatis-config.xml中的<mappers>元素下添加的代码如下:
<mapper resource="com/itheima/mapper/StudentMapper.xml"/>
(5)在项目的 src/test/java 目录下的 Test 包中创建测试类MyBatisTest,在测试类中编写测试方法findAllStudentTest(),用于测试<resultMap>元素实现查询结果的映射。MyBatisTest类的具体实现代码如文件2-3所示。
1 package Test; 2 import com.itheima.pojo.Student; 3 import org.apache.ibatis.io.Resources; 4 import org.apache.ibatis.session.SqlSession; 5 import org.apache.ibatis.session.SqlSessionFactory; 6 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 7 import org.junit.After; 8 import org.junit.Before; 9 import org.junit.Test; 10 import java.io.IOException; 11 import java.io.Reader; 12 import java.util.List; 13 public class MyBatisTest { 14 private SqlSessionFactory sqlSessionFactory; 15 private SqlSession sqlSession; 16 @Before 17 public void init() { 18 //定义读取文件名 19 String resources = "mybatis-config.xml"; 20 //创建流 21 Reader reader = null; 22 try { 23 //读取mybatis-config.xml文件到reader对象中 24 reader = Resources.getResourceAsReader(resources); 25 //初始化mybatis,创建SqlSessionFactory类的对象 26 SqlSessionFactory sqlMapper = new 27 SqlSessionFactoryBuilder().build(reader); 28 //创建sqlSession对象 29 sqlSession= sqlMapper.openSession(); 30 } catch (IOException e) { 31 e.printStackTrace(); 32 } 33 } 34 @Test 35 public void findAllStudentTest() { 36 // SqlSession执行映射文件中定义的SQL,并返回映射结果 37 List<Student> list = 38 sqlSession.selectList("com.itheima.mapper.StudentMapper.findAllStudent"); 39 for (Student student : list) { 40 System.out.println(student); 41 } 42 } 43 @After 44 public void destory() { 45 //提交事务 46 sqlSession.commit(); 47 //关闭事务 48 sqlSession.close(); 49 } 50 }
在文件2-3中,第16~33行代码编写了一个init()方法,并使用@Before注解标注。JUnit中的@Before注解用于初始化,每个测试方法都要执行一次init()方法。在init()方法中,先根据MyBatis的核心配置文件构建SqlSessionFactory对象,再通过SqlSessionFactory对象创建SqlSession对象。
第34~42行代码编写了一个测试方法findAllStudentTest(),在该方法中,第37行和第38行代码调用Sql-Session 对象的 selectList()方法执行查询操作。selectList()方法的参数是映射文件 StudentMapper.xml 中定义的<select>元素id的全限定类名,这里为“com.itheima.mapper.StudentMapper.findAllStudent”,表示将执行这个id所表示的<select>元素中的SQL语句。第39~41行代码用于遍历执行SQL语句并返回结果集。
第43~49行代码定义了一个destory()方法,用于释放资源,并使用@After注解标注,使用@After注解标注的方法在每个方法执行完后都要执行一次。
需要注意的是,在测试类MyBatisTest中,每一个用@Test注解标注的方法称为测试方法,它们的调用顺序为@Before→@Test→@After。
运行MyBatisTest 测试类,控制台的输出结果如图2-3所示。
对图2-3中的运行结果进行分析可知,虽然 t_student数据表的列名与Student对象的属性名完全不一样,但查询出的数据还是被正确地封装到了 Student 对象中,这表明<resultMap>元素实现了查询结果的映射。
图2-3 MyBatisTest测试类运行结果
在上述案例中,由于每个方法执行时都需要读取配置文件,并根据配置文件的信息构建 SqlSessionFactory对象、创建SqlSession 对象、释放资源,这产生了大量的重复代码。为了简化开发,可以将读取配置文件和释放资源的代码封装到一个工具类中,然后通过工具类创建SqlSession对象。工具类具体代码如文件2-4所示。
1 package com.itheima.utils; 2 import java.io.Reader; 3 import org.apache.ibatis.io.Resources; 4 import org.apache.ibatis.session.SqlSession; 5 import org.apache.ibatis.session.SqlSessionFactory; 6 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 7 /** 8 * 工具类 9 */ 10 public class MyBatisUtils { 11 private static SqlSessionFactory sqlSessionFactory = null; 12 // 初始化SqlSessionFactory对象 13 static { 14 try { 15 // 使用MyBatis提供的Resources类加载MyBatis的配置文件 16 Reader reader = 17 Resources.getResourceAsReader("mybatis-config.xml"); 18 // 构建SqlSessionFactory 19 sqlSessionFactory = 20 new SqlSessionFactoryBuilder().build(reader); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } 24 } 25 // 获取SqlSession对象的静态方法 26 public static SqlSession getSession() { 27 return sqlSessionFactory.openSession(); 28 } 29 }
这样,在使用时就只创建了一个SqlSessionFactory对象,并且可以通过工具类的getSession()方法来获取SqlSession对象。