2.2、MyBatis的XML基本用法——select用法
select用法
使用纯粹的JDBC时,需要写查询语句,并且对结果集进行手动处理,将结果映射到对象的属性中
使用 MyBatis 时,只需要在XML中添加一个select元素,写一个SQL,再做一些简单的配置,就可以将查询的结果直接映射到对象中
下面以用id查找用户为例,讲解select用法
1、添加对应接口方法selectById
public interface UserMapper {
/**
* 通过 id 查询用户
*
* @param id
* @return
*/
SysUser selectById(Long id);
}
2、添加对应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="tk.mybatis.simple.mapper.UserMapper">
<resultMap id="userMap" type="tk.mybatis.simple.model.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="userMap">
select * from sys_user where
id = #{id}
</select>
</mapper>
接口与XML的关联:通过XML的<mapper>标签的namespace值设置为接口的全限定名称进行关联
接口中方法与XML的关联:通过XML的<select>标签的id值设置为接口方法名称进行关联
3、XML的设计规则:
a、只使用XML而不使用接口时,namespace的值可以设置为任意不重复的值
b、标签的 id 属性值在任何时候都不能出现英文句号 “.” ,并且同一个命名空间下不能出现重复的id
c、因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是 XML 中 id 的值不能重复,因而接口中的所有同名方法会对应着 XML 中的同一个 id 的方法。最常见的用法就是,同名方法中其中一个方法增加一个 RowBound 类型的参数用于实现分页查询
4、XML的标签与属性
<select>标签:映射查询语句使用的标签
id:命名空间中的唯一标识符,可用来代表这条语句
resultMap:用于设置返回值的类型和映射关系
resultType:直接自动影射为某对象
select * from sys_user where id=#{id} 是查询语句
#{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的 id 是传入的参数名
在上面的select中,使用resultMap设置返回值的类型,这里的userMap就是上面<resultMap>中的id属性值,通过id引用需要的<resultMap>
<resultMap>标签:用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对象的属性上,因此当我们使用select*查询所有列的时候,MyBatis也可以将结果正确地映射到SysUser对象上
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对象属性的普通结果。
<idArg>、<arg>和<id>、<result>的含义相同,不同在于前者是通过类的构造函数注入,后者是通过属性的setter方法注入
<association>标签:一个复杂的类型关联,许多结果将包成这种类型。
<collection>标签:复杂类型的集合。
<discriminator>标签:根据结果值来决定使用哪个结果映射。
<case>标签:基于某些值的结果映射。
<id>和<result>里面的属性
column:从数据库中得到的列名,或者是列的别名。
property:映射到列结果的属性。可以映射简单的如“username”这样的属性,也可以映射一些复杂对象中的属性,例如“address.street.number”,这会通过“.”方式的属性嵌套赋值。
javaType:一个Java类的完全限定名,或一个类型别名(通过typeAlias配置或者默认的类型)。如果映射到一个JavaBean,MyBatis通常可以自动判断属性的类型。如果映射到HashMap,则需要明确地指定javaType属性。
jdbcType:列对应的数据库类型。JDBC类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。这是JDBCjdbcType的需要,而不是MyBatis的需要。
typeHandler:使用这个属性可以覆盖默认的类型处理器。这个属性值是类的完全限定名或类型别名
如何定义返回值
resultMap:用resultMap来设置映射
resultType:根据读取结果和对应类自动映射,可以在select中使用别名,来和类的属性名对应
5、多结果查询例子
接口方法
/**
* 查询全部用户
*
* @return
*/
List<SysUser> selectAll();
XML设置
<select id="selectAll" resultType="tk.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>
这个例子中,select标签中直接使用resultType,以及使用字段别名,使sql的字段名与类的字段名对上,即可自动映射
6、名称映射规则
综上,有2种方式:1、在resultMap中配置property属性和column属性的映射;2、SQL中设置别名
property或者别名要与对象的属性名对应才能匹配,实际匹配时,是把字母都转换成大写来匹配的,所以不区分大小写;一般为了阅读,应该统一写法就OK
一种很常见的情况,数据库使用下划线命名方式,如user_Name;而Java中使用驼峰式命名,如userName
MyBatis提供了一个配置,自动将这2种方式进行匹配,在配置文件中设置即可,代码如下
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
7、添加单元测试代码
在src/test/java下的tk.mybatis.simple.mapper包中,添加基础测试类
package tk.mybatis.simple.mapper;
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);
reader.close();
} catch (IOException ignore) {
ignore.printStackTrace();
}
}
public SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
修改CountryMapperTest类
因为Country和User的Mapper中都有SelectAll,所以不在唯一,需要用全限定类名tk.mybatis.simple.mapper.CountryMapper.selectAll去调用
package tk.mybatis.simple.mapper;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import tk.mybatis.simple.model.Country;
public class CountryMapperTest extends BaseMapperTest {
@Test
public void testSelectAll() {
SqlSession sqlSession = getSqlSession();
try {
List<Country> countryList = sqlSession.selectList("tk.mybatis.simple.mapper.CountryMapper.selectAll");
printCountryList(countryList);
} finally {
sqlSession.close();
}
}
private void printCountryList(List<Country> countryList) {
for (Country country : countryList) {
System.out.printf("%-4d%4s%4s\n", country.getId(), country.getCountryname(), country.getCountrycode());
}
}
}
修改mybatis-config.xml,把CountryMapper.xml加入到Mapper配置中
<mappers>
<mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml" />
<package name="tk.mybatis.simple.mapper" />
</mappers>
完成上面设置后即可进行单元测试
8、一些复杂用法
a、关联查找某个表的数据
接口方法
/**
* 根据用户 id 获取角色信息
*
* @param userId
* @return
*/
List<SysRole> selectRolesByUserId(Long userId);
XML配置
<select id="selectRolesByUserId" resultType="tk.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",
u.user_email as "user.userEmail"
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>
因为使用了自定义类型Enable,所以在mybatis-config.xml加入自定义类型处理器
<typeHandlers>
<typeHandler
javaType="tk.mybatis.simple.type.Enabled"
handler="tk.mybatis.simple.type.EnabledTypeHandler"/>
</typeHandlers>
这种情况,虽然有关联表查询,但是只是一个实体的数据,所以只是sql中加入了join语句的不同,其它和单个表查询基本相同
b、关联查询多个表数据
例如a中,我不仅要查询角色,也要带上用户的信息,那就是要查询出2个表的信息了,那么可以如下这么做
第一种:把用户的信息增加到角色表中;不过这种方式不建议
package tk.mybatis.simple.model;
public class SysRoleExtend extends SysRole {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
第二种:把user表加到角色表中
public class SysRole implements Serializable {
/**
* 用户信息
*/
private SysUser user;
......
修改mapper,使用“.”的方式增加user需要的信息
<select id="selectRolesByUserId" resultType="tk.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",
u.user_email as "user.userEmail"
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>
测试代码
@Test
public void testSelectRolesByUserId(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用 selectRolesByUserId 方法查询用户的角色
List<SysRole> roleList = userMapper.selectRolesByUserId(1L);
//结果不为空
Assert.assertNotNull(roleList);
//角色数量大于 0 个
Assert.assertTrue(roleList.size() > 0);
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}