我们执行查询操作,使用MyBatis,我们只需要在XML中添加select元素,然后写上SQL语句,然后再做一些简单的配置,就可以将查询的结果直接映射到对象中。
MyBatis参考文档:
中文版:http://www.mybatis.org/mybatis-3/zh/index.html
英文版:http://www.mybatis.org/mybatis-3/
工具
JDK 1.6及以上版本
MyBatis 3.30版本
MySQL 6.3版本
Eclipse4 及以上版本
Apache Maven 构建工具
项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex
先写一个根据用户id,查询用户信息的简单方法。在UserMapper接口中添加一个selectById方法。
package mybatis.simple.mapper;
import mybatis.simple.model.SysUser;
public interface UserMapper {
public SysUser selectById(Long id);
}
然后在对应的UserMapper.xml中添加如下的<resultMap>和<select>部分的代码。
<?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="mybatis.simple.mapper.UserMapper">
<resultMap type="mybatis.simple.model.SysUser" id="SysUser">
<id property="id" column="id" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="userEmail" column="user_email" />
<result property="userInfo" column="user_info" />
<result property="headImg" column="head_img" jdbcType="BLOB" />
<result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
</resultMap>
<select id="selectById" resultMap="SysUser">
select * from sys_user where id = #{id}
</select>
</mapper>
创建接口和XML时我们知道接口和XML是通过将namespace的值设置为接口的全限定名称来进行关联的,那么接口中的方法和XML又是怎么关联的呢?
XML中的select标签的id属性值和定义的接口方法名一样,MyBatis就是通过这种方式将接口方法和XML中定义的SQL语句关联到一起的。如果接口方法没有和XML中的id属性值相对应,启动程序便会报错。
映射XML和接口的命名需要符合规则如下:
① 当只使用XML而不使用接口的时候,namespace的值可以设置为任意不重复的名称。
② 标签的id属性值在任何时候都不能出现英文的句号“.”,并且同一个命名空间下不能出现重复的id
③ 因为接口方法是可以重载的,所以接口中可以出现多个同名但是参数不同的方法,但是XML中id的值不能重复,那么接口中的多有的同名方法都对应着XML中的同一个id的方法。
标签和属性的作用:
- <select>:映射查询语句使用的标签。
- id:命名空间中唯一标识符,可用来代表这条语句
- resultMap:用于设置返回值得类型和映射关系。
- select标签中的select * from sys_user where id = #{id}是查询语句。
- #{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的id是传入的参数名。
在上面的<select>中,使用resultMap设置返回值得类型,这里的SysUser就是<resultMap>中的id属性值,通过id引用需要的<resultMap>。
resultMap标签用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对应的属性上,因此当我们使用select *进行查询所有列时,MyBatis也可以将结果正确地映射到SysUser对象上。
resultMap包含的所有属性:
- id:必填,并唯一,在select标签中resultMap指定的值即为此id所设置的值。
- type:必填用于配置查询列所映射到的Java对象类型。
- extends:选填,可以配置当前的resultMap继承自其它的resultMap,属性值为继承的resultMap的id。
- autoMapping:选填,可选值为true或false,用于配饰是否启用非映射字段(没有在resultMap中配置的字段)的自动映射功能,该配置可以覆盖全局的autoMappingBehavior配置。
resultMap包含的所有标签:
- constructor:配置使用构造方法注入结果,包含以下两个字标签
idArg:id参数,标记结果作为id(唯一值)
arg:注入到构造方法中的普通结果
- id:一个id结果,标记作为id(唯一值)
- result:注入到Java对象属性的普通结果。
- association:一个复杂的类型观念,许多结果将包成这种类型
- collection:复杂类型的集合。
- discriminator:根据结果值来决定使用哪个结果映射
- case:基于某些值的结果映射。
constructor通过构造方法注入属性的结果值。构造方法中的idArg、arg参数分别对应着resultMap标签中的id、result标签,它们的含义相同,只是注入的方式不同。
resultMap中的id和result标签包含的属性相同,不同的地方在于id代表的是主键(或唯一键)的字段(可以有多个),它们的属性值是通过setter方法注入的。
id和result标签包含的属性:
- column:从数据库得到的列名,或者是列的别名。
- property:映射到列结果的属性(JavaBean的属性)
- javaType:一个Java类的完全限定名,或一个类型别名(通过mybatis-config.xml中的typeAlias配置或者默认的类型)。如果映射到一个JavaBean,MyBatis通常可以自动判断属性的类型。
- jdbcType:列对应的数据库类型。JDBC类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。
- typeHandler:使用这个属性可以覆盖默认的类型处理器。
接口中定义的返回值类型必须和XML中配置的resultType类型一致,否则就会因为类型不一致而抛出异常。返回值类型是由XML中的resultType(或者resultMap中的type)决定,不是由接口中写的返回值类型决定的。
UserMapper接口中的selectById方法,通过主键id查询,最多只会返回一条数据,所以这里的返回值是SysUser.
我们再写一个查询所有用户的方法,在UserMapper接口中添加selectAll方法;
package mybatis.simple.mapper;
import java.util.List;
import mybatis.simple.model.SysUser;
public interface UserMapper {
public SysUser selectById(Long id);
public List<SysUser> selectAll();
}
并在对应的UserMapper.xml中添加新的查询所有用户的<select>部分:
<?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="mybatis.simple.mapper.UserMapper">
<resultMap type="mybatis.simple.model.SysUser" id="SysUser">
<id property="id" column="id" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="userEmail" column="user_email" />
<result property="userInfo" column="user_info" />
<result property="headImg" column="head_img" jdbcType="BLOB" />
<result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
</resultMap>
<select id="selectById" resultMap="SysUser">
select * from sys_user where id = #{id}
</select>
<select id="selectAll" resultType="mybatis.simple.model.SysUser">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
</select>
</mapper>
观察我们selectById和selectAll我们可以发现:
1、selectById中的返回最多只有1个结果时,我们可以直接使用JavaBean对象来接收返回数据。而当执行的SQL返回多个结果时,接口必须使用List<Object>或Object[]作为返回值(Object为具体的JavaBean对象)。
2、使用resultMap和resultType都可以返回对象集合,使用resultType限定返回类型时,属性值为返回对象的权限定类名,使用resultMap限定返回类型时,属性值为resultMap标签中定义的id的值。
3、如果使用resultType来设置返回结果的类型,需要在SQL中为所有的列名和属性名不一致的列设置别名,通过设置别名来使最终的查询结果列和resultType指定对象的属性名保持一致,进而实现自动映射。
名称映射规则:
可以在resultMap中配置property属性和column属性的映射,或者在SQL中设置别名,这两种方式实现将查询列映射到对象属性的目的。
property属性或别名要和对象中属性的名字相同,但是实际匹配时,MyBatis会先将两者都转换为大写形式,然后再判断是否相同,即property=“userName”和proeprty="username"都可以匹配到对象的userName属性上。判断是否相同的时候要使用USERNAME,因此在设置property属性或别名的时候,不需要考虑大小写是否一致。
下面通过测试来验证我们的两个查询。
首先我们提取出一个基础测试类BaseMapperTest,而其他测试类,都继承此类,通过此类提供的getSqlSession()获取SqlSession对象。
BaseMapperTest基础测试类:
package mybatis.simple.test;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.BeforeClass;
public class BaseMapperTest {
private static SqlSessionFactory sqlSessionFactory;
@BeforeClass
public static void init(){
try{
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}catch (IOException e) {
e.printStackTrace();
}
}
public SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
编写UserMapperTest测试类;
package mybatis.simple.test;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import mybatis.simple.mapper.UserMapper;
import mybatis.simple.model.SysUser;
public class UserMapperTest extends BaseMapperTest {
@Test
public void testSelectById(){
//获取SqlSession
SqlSession sqlSession = getSqlSession();
//获取UserMapper接口
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用selectById方法,查询id=1的用户
SysUser user = userMapper.selectById(1L);
} finally{
//关闭SqlSession
sqlSession.close();
}
}
@Test
public void testSelectAll(){
//获取SqlSession
SqlSession sqlSession = getSqlSession();
//获取UserMapper接口
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用selectById方法,查询id=1的用户
List<SysUser> userList = userMapper.selectAll();
} finally{
//关闭SqlSession
sqlSession.close();
}
}
}
右键单击该类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 511473681.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - ==> Preparing: select id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime from sys_user
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime
TRACE [main] - <== Row: 1, admin, 123456, admin@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:26:52.0
TRACE [main] - <== Row: 2, test, 123456, test@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:27:30.0
DEBUG [main] - <== Total: 2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Returned connection 511473681 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 511473681 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - ==> Preparing: select * from sys_user where id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <== Row: 1, admin, 123456, admin@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:26:52.0
DEBUG [main] - <== Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Returned connection 511473681 to pool.
上面的两个SELECT查询仅仅是简单的单表查询,而我们在实际情况下,通常需要进行多表关联查询,关联查询的结果也会有多种情况。
第一种情况:根据用户id获取用户拥有的所有角色,返回的结果为角色集合,结果只有角色的信息,不包含额外的其他字段信息。
在UserMapper接口中添加一个方法selectRoleByUserId
public List<SysRole> selectRoleByUserId(Long userId);
并在UserMapper.xml中添加如下代码;
<select id="selectRoleByUserId" resultType="mybatis.simple.model.SysRole">
select r.id,
r.role_name roleName,
r.enabled,
r.create_by createBy,
r.create_time createTime
from sys_user_role ur , sys_role r where
ur.role_id=r.id
and ur.user_id =
#{userId}
</select>
虽然这是从多张表中查询出的数据,但是结果值包含一个表(sys_role)中的信息,所以直接返回SysRole即可。
第二种情况:在第一种情况的基础下,返回的结果不仅包含角色的信息,还包含当前用户的部分信息(用户名)。
实现此种情况,我们有三种方法
1、在SysRole对象中直接添加userName属性,这样仍然使用SysRole作为返回值。
public class SysRole {
//其他原字段
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
//原字段的getter和setter
}
2、新建一个类,继承SysRole,在该类中添加属性userName,最后返回该类即可。
package mybatis.simple.model;
public class SysRoleUser extends SysRole{
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
3、将用户对象最为一个属性添加到角色中
public class SysRole {
//其他原字段
private SysUser user;
//原字段的getter和setter
}
直接在SysRole中增加SysUser对象,字段名为user,增加这个字段后,修改XML中的selectRoleByUserId方法
<select id="selectRoleByUserId" resultType="mybatis.simple.model.SysRole">
select r.id,
r.role_name roleName,
r.enabled,
r.create_by createBy,
r.create_time createTime,
u.user_name as "user.userName"
from sys_user u
inner join sys_user_role ur on u.id=ur.user_id
inner join sys_role r on ur.role_id=r.id
where u.id =
#{userId}
</select>
注意 u.user_name as "user.userName",这里设置别名时,使用的是“user.属性名”,user是SysRole中添加的属性,userName是SysUser对象中的属性,通过这种方式直接将值赋给user字段中的属性。
在UserMapperTest中添加测试代码:
@Test
public void testSelectRoleByUserId() {
// 获取SqlSession
SqlSession sqlSession = getSqlSession();
// 获取UserMapper接口
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用selectById方法,查询id=1的用户
List<SysRole> roleList = userMapper.selectRoleByUserId(1L);
} finally {
// 关闭SqlSession
sqlSession.close();
}
}
右键单击该类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 2011986105.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - ==> Preparing: select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime, u.user_name as "user.userName" from sys_user u inner join sys_user_role ur on u.id=ur.user_id inner join sys_role r on ur.role_id=r.id where u.id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, roleName, enabled, createBy, createTime, user.userName
TRACE [main] - <== Row: 1, 管理员, 1, 1, 2017-08-09 15:26:52.0, admin
TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2017-08-09 15:26:52.0, admin
DEBUG [main] - <== Total: 2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - Returned connection 2011986105 to pool.
从输出的日志中我们可以很明显的看到增加了用户名的列数据。
总结:
① SELECT语句返回的数据只为一个对象(表)的数据,可以直接使用对象接收。
② SELECT语句返回的数据为多个对象(多个表)中的字段,此时可以新建一个类,包含所有要返回的字段,然后使用该类作为返回数据类型。
③ SELECT语句返回的数据包含大量的其他对象(表)的字段,则可以将额外字段所属对象最为属性,添加到主对象中,而后在配置Mapper.xml中的SQL语句时,采用“对象.属性”的方式作为别名,将值赋给额外的字段。
项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex