Mapper XML
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。每一个dao层接口对应一个Mapper xml,需要注意的是Mapper xml的namespace需要为对应映射接口的全限定名。我们通过SqlSession获得接口的代理类,代理类基于接口的名称匹配namespace,从而才能找到对应的Mapper xml
//3.创建SqlSession对象
session = sqlSessionFactory.openSession();
//获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
StudentMapper mapper = session.getMapper(StudentMapper.class);
//调用接口中方法,实际执行映射为mapper下sql语句
mapper.updateNameById(id, name);
SQL映射文件的顶级元素:
- cache – 给定命名空间的缓存配置
- cache-ref – 其他命名空间缓存配置的引用
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
- sql – 可被其他语句引用的可重用语句块
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
select
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值
注意参数符号:
#{id}
这就告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
select元素有很多属性供用户选择:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句;需要和接口中方法名一致 |
parameterType | 将会传入这条语句的参数类型,可以是全限定名或者是别名;当有多个参数时传入map;这个属性是可选的,因为MyBatis可以通过TypeHandler推断出具体传入语句的参数 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false |
useCache | 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动) |
fetchSize | 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动) |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED |
resultSets | 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的 |
… | … |
select返回list类型
<!-- list返回类型为集合中的元素类型-->
<select id="selectByLikeName" resultType="com.Reyco.beans.Student">
select * from Student where name like #{likeName}
</select>
List<Student> list = studentDaoImpl.selectByLikeName("%e%");
for(Student student : list) {
System.out.println(student);
}
Student [name=Reyco, id=3, age=21, score=99.0]
Student [name=Abenl, id=4, age=22, score=100.0]
select返回Map类型
#select返回单个Map
//Map的key为字段名,value为字段内容
Map<String,Object> selectByIdreturnMap(Integer id);
<!-- 返回单个map,返回值类型为map-->
<select id="selectByIdreturnMap" resultType="map">
select * from Student where id = #{id}
</select>
studentDaoImpl studentDaoImpl = new studentDaoImpl();
Map<String,Object> map=studentDaoImpl.selectByIdreturnMap(3);
System.out.println(map);
{score=99.0, name=Reyco, id=3, age=21}
//Map的key为字段名,value为字段内容
#select返回多个Map
//返回多个Map时,需要用注解表明Map的主键名
@MapKey("id")
Map<Integer,Student> selectByIdreturnMapList(String lastName);
<!-- 返回多个map,返回类型为Map中元素类型-->
<select id="selectByIdreturnMapList" resultType="com.Reyco.beans.Student">
select * from Student where name like #{likeName}
</select>
studentDaoImpl studentDaoImpl = new studentDaoImpl();
Map<Integer,Student> map =studentDaoImpl.selectByIdreturnMapList("%e%");
System.out.println(map);
{3=Student [name=Reyco, id=3, age=21, score=99.0], 4=Student [name=Abenl, id=4, age=22, score=100.0]}
resultMap
resultMap-自定义结果集映射,实现高级结果集映射,所谓结果集映射就是将数据库返回的结果映射java对象,即哪个属性对应哪个字段。
全局setting结果集映射设置
- autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean的属性名一致
- 数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMN->aColumn,我们可以开启驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
Student selectById(Integer id);
<resultMap type="com.Reyco.beans.Student" id="stu">
<id column="id" property="id"/>
<result column="age" property="age"/>
<result column="name" property="name"/>
<result column="score" property="score"/>
</resultMap>
<select id="selectById" resultMap="stu">
select * from Student where id = #{id}
</select>
resultMap关联查询
为Student类中添加Teacher属性,使用关联查询
private String name;
private Integer id;
private int age;
private double score;
private Teacher teacher; //联合对象
<select id="selectByIdContainTeacher" resultMap="temp">
select s.name, t.teacherName ,s.id,s.age,s.score,t.id
from student s , teacher t
where s.teacher =t.id and s.id =#{id};
</select>
resultMap使用级联查询实现关联
<resultMap type="com.Reyco.beans.Student" id="temp">
<id column="s.id" property="id"/>
<result column="s.age" property="age" />
<result column="s.name" property="name"/>
<result column="s.score" property="score"/>
<!-- -Student内属性teacher->
<!-- 通过级联查询关联查询-->
<result column="t.teacherName" property="teacher.teacherName"/>
<result column="t.id" property="teacher.id"/>
</resultMap>
resultMap使用Assoiation标签实现关联查询
<resultMap type="com.Reyco.beans.Student" id="temp2">
<id column="s.id" property="id"/>
<result column="s.age" property="age" />
<result column="s.name" property="name"/>
<result column="s.score" property="score"/>
<!-- association可以指定联合的javaBean对象-->
<!-- property指定联合对象-->
<!-- javaType指定联合对象对应全限定类名-->
<association property="teacher" javaType="com.Reyco.beans.Teacher">
<id column="tid" property="id"/>
<result column="n" property="teacherName"/>
</association>
</resultMap>
resultMap使用Associaion标签实现分步查询
<select id="selectByStep" resultMap="temp3">
select s.id id,s.name name,s.age age,s.score score,s.teacher tt from Student s where id = #{id}
</select>
<select id="selectByIdFindTeacher" resultType="com.Reyco.beans.Teacher">
select * from teacher where id =#{id}
</select>
<resultMap type="com.Reyco.beans.Student" id="temp3">
<id column="id" property="id"/>
<result column="age" property="age" />
<result column="name" property="name"/>
<result column="score" property="score"/>
<!-- property指定联合对象名称,select指定分步查询方法,将column中的字段(第一步查询出的字段)作为参数传递进select,将结果返回封装给teacher-->
<association property="teacher" select="selectByIdFindTeacher" column="tt"></association>
</resultMap>
resultMap使用Association标签实现延迟加载
全局配置下:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
resultMap使用Collection标签定义关联集合
Teacher类中有List< Student >属性,可以使用resultMap下的Collection标签获取关联集合
private Integer id;
private String teacherName;
private List<Student> students;
<select id="selectByIdFindTeacherPlus" resultMap="temp4">
select t.teacherName tname,t.id tid,s.name sname,s.id sid , s.age sage,s.score sscore
from teacher t
LEFT JOIN student s
on t.id = s.teacher
where t.id =#{id};
</select>
<resultMap type="com.Reyco.beans.Teacher" id="temp4">
<id column="tid" property="id"/>
<result column="tname" property="teacherName"/>
<!-- property定义集合关联对象-->
<!-- ofType指定集合内元素-->
<collection property="students" ofType="com.Reyco.beans.Student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<result column="sscore" property="score"/>
</collection>
</resultMap>
collection标签可以和association标签可以实现分步查询,一样有select属性和column属性,用法相同。
collection和association
association针对联合对象,而collection针对联合集合
insert, update 和 delete
数据变更语句 insert,update 和 delete 的实现非常接近:
<insert id="insertStudent" parameterType="com.Reyco.beans.Student" useGeneratedKeys="true" keyProperty="id">
insert into Student(name,age,score) values(#{name}, #{age}, #{score})
</insert>
<update id="updateNameById">
update Student
set name=#{name}
where id=#{id}
</update>
<delete id="deleteById" parameterType="int">
delete from Student where id=#{id}
</delete>
属性 | 描述 |
---|---|
同上 | 同上 |
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表 |
… | … |
Sql
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个 SQL 片段可以被包含在其他语句中,例如:
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
参数
MyBatis对于Sql语句参数的处理是非常强大的。
<select id="selectUsers" resultType="User">
select id, username, password
from users
<!-- 传入参数类型为int的id-->
where id = #{id}
</select>
上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为 int,这样这个参数就可以被设置成任何内容。原生的类型或简单数据类型(比如整型和字符串)因为没有相关属性,它会完全用参数值来替代。
当传入参数为POJO对象时,行为有点不同:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
当User类型的参数对象传递到了语句中,id和username和password将会被自动在User类型的参数对象中查找,然后将它们的值传入预处理语句的参数中。
多个参数的传入而且这些参数并不都是封装在某个对象中的,这时候情况会稍显复杂:
任意多个参数,都会被MyBatis重新包装成Map传入,Map的key是param1,param2.索引…对应的value就是参数的值
void updateNameById(Integer id,String name);
<update id="updateNameById">
update Student
set name=#{param1}
where id=#{param2}
</update>
命名参数
为参数使用@Param起一个名字,MyBatis会将参数封装进Map
void updateNameById(@Param("id")Integer id,@Param("name")String name);
<update id="updateNameById">
update Student
set name=#{name}
where id=#{id}
</update>
剖析参数处理源码
从调用代理类MapperProxy中invoke方法为入口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断该方法是否为Object下方法(toString,hashCode等方法),如果是直接返回并执行,不进行包装
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//将method包装成MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//MapperMethod下execute方法对传入方法进行执行
return mapperMethod.execute(sqlSession, args);
}
进入MapperMethod下execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断Sql类型
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
//result为返回值
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//无返回值
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回多个
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
//跟踪convertArgsToSqlCommandParam
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
进入convertArgsToSqlCommandParam,封装参数的代码
public Object convertArgsToSqlCommandParam(Object[] args) {
//1params为一个Map,存储参数的Map
//1.1获取每个标了param注解参数的值,赋值给param
//1.2每个解析一个参数给map中保存信息,没有标注param注解的,则param=参数名
//获取参数数量
final int paramCount = params.size();
//参数为null直接返回
if (args == null || paramCount == 0) {
return null;
//2如果只有一个元素,并且没有注解,直接args[0]
//单元素直接返回
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
}
//2,多个元素或者标有param
else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//遍历params集合{0=id,1=firstName,...索引=参数名}
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//params集合的value(参数名)作为key,params的key-索引作为取值的参考args[0]:传入参数值-->args[1,"Tom"]
//{id=args[0]:1,firstName=args[1]:Tom}-->这样就可以使用#{}传入值
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// 将每一个参数都保存到Map中,使用param1,param2...作为key
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
关于params
//存储参数的Map
private final SortedMap<Integer, String> params;
参数值的获取
**#{ }**可以获取Map中的值或者pojo对象中的值
**${ }**可以获取Map中的值或者pojo对象中的值
区别:
#{ }:是以预编译的形式,将参数设置到sql语句中–PreparedStatement,能防止sql注入
${ }:取出的值直接拼装在sql语句中,无法防止sql注入
大多数情况下,参数值的获取都是 #{ }
原生jdbc不支持占位符的地方可以使用 ${ }:比如分表,排序等
#{ }取值时规定参数规则
参数位置支持的属性
javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName
实际上通常被设置的是:可能为空列名指定jdbcType
MyBatis会将null映射成Other类型,而Oracle不兼容Other类型,需要对可能为空的列名指定jdbcType为NULL
如#{email,jdbcType=NULL}