内容:
- select用法
- insert用法
- update用法
- delete用法
- 多个接口参数的用法
- 动态代理实现原理
select用法
MyBatis的select用法,重点是resultMap、resultType的使用
先上一段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 user_name,
user_email,
user_info
from sys_user
where id = #{id}
</select>
</mapper>
需要注意的内容:
- MyBatis是使用XML中的select标签的id属性值和定义的接口方法名相一致来将接口方法和XML中定义的SQL语句关联到一起的,如果接口方法没有和XML中的id属性值相对应,启动程序便会报错。
- 当只是用XML而不使用接口的时候,namespace的值可以设置为任意不重复的名称
- 标签的id属性值在任何时候都不能出现英文句号“.”,并且同一命名空间下不能出现重复的id
- 因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是XML中id的值不能重复,因而接口中所有同名方法会对应着XML中的同一个id的方法
resultMap标签用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到对象的属性上。
resultMap标签包含的所有属性有:
- id:必填项,唯一,用于在namespace中标示resultMap
- type:必填项,用于配置查询列所映射到的Java对象类型
- extends:可选项,可以配置当前的resultMap继承自其它的resultMap,属性值为继承resultMap的id
- autoMapping:可选项,可选值为true或false,用于配置是否启用非映射字段(没有在resultMap中配置的字段)的自动映射功能,该配置可以覆盖全局的autoMappingBehavior配置
resultMap包含的标签有:
- id:标记结果作为唯一值,可以帮助提高整体性能
- result:注入到Java对象属性的普通结果
- constructor:配置使用构造方法注入结果
id标签和result标签包含的属性值:
- column:从数据库中得到的列名,会这是列的别名
- property:映射到列结果的属性,可以简单映射,也可以借助“.”进行嵌套映射
- javaType:Java类的完全限定名
- jdbcType:列对应的数据库类型
- typeHeader:使用这个属性可以覆盖默认的烈性处理器,属性值为类的完全限定名或类型别名
需要注意的内容:
可以通过在resultMap中配置property和column属性的映射,或者在SQL中设置别名这两种方式实现将查询列映射到对象属性上。
property属性或别名要和对象中属性的名字相同,但实际匹配时,MyBatis会先将两者转换为大写大写形式,然后再判断是否相同。假设对象有一个userName属性,那么property="username"或property="userName"都可以匹配到对象的userName属性。
因此,在设置property属性或别名的时候,不需要考虑大小写是否一致,但为了便于阅读,尽量按统一的规则来设置。
- MyBatis自带了将下划线的命名转化为驼峰式的命名的功能,要想启用这个功能,只需要在MyBatis的配置文件中增加如下配置:
<setting name="mapUnderscoreToCamelCase" value="true" />
constructor标签包含的子标签有:
- idArg:标记结果作为唯一值,可以帮助提高整体性能
- arg:注入到构造方法的一个普通结果
constructor中的idArg、arg分别对应着resultMap中的id、result标签,它们的含义相同,只是注入方式不同
考虑一种情况:在使用resultType的前提下,假设有用户表、角色表、权限表
第一种简单的情形:根据用户id获取用户拥有的所有角色,返回的结果为角色集合,结果只有角色的信息,不包含额外的其他字段信息,这时直接使用resultType="SysRole"即可。
第二种情形:根据用户id获取用户拥有的所有角色,并且需要查询出用户名和用户邮件。
第一种解决方法就是在SysRole对象中直接添加userName属性和userEmail属性,这样仍然使用SysRole作为返回值即可。
第二种方法:创建一个如下所示的对象:
public class SysRoleExtend extends SysRole {
private String userName;
private String userEmail;
}
这样就可以将resultType设置为SysRoleExtend即可。
- 第三种方法:直接在SysRole中添加SysUser对象,字段名为user。增加了这个字段后,修改对应的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>
user是SysRole中刚刚增加的属性,userName和userEmail是SysUser对象中的属性,通过这种方式可以直接将值赋给user字段中的属性
insert用法
insert的用法比较简单,只有让它返回生成的主键值时,由于不同数据库的主键生成方式不同,这种情况会有一些复杂。
insert标签包含的属性有:
id:命名空间中的唯一标识符。
parameterType:传入的参数的完全限定类名或别名。这个属性是可选的,因为MyBatis可以推断出传入语句的具体参数,因此不建议配置该属性。
flushCache:默认值为true,任何时候只要语句被调用,都会清空一级缓存和二级缓存。
timeout:驱动程序等待数据库返回请求结果的秒数。
statementType:可选值有STATEMENT、PREPARED、CALLABLE,MyBatis会分别使用对应的Statement、PrepareStatement、CallableStatement,默认值为PREPARED。
useGeneratedKeys:默认值为true。如果设置为true,MyBatis会使用JDBC的getGeneratedKeys方法来去除由数据库内部生成的主键。
keyProperty:MyBatis通过getGeneratedKeys获取主键值后将要赋值的对象的属性名。如果希望得到多个数据库自动生成的列,属性值也可以是以逗号分隔的属性名称列表
databaseId:多数据库支持。前提是在MyBatis-config.xml中配置了databaseIdProvider。如果配置了,那么MyBatis会加载所有不带databaseId或匹配当前databaseId的语句。如果同时存在带databaseId和不带databaseId的语句,后者会被忽略。
简单的insert
上一段简单的XML代码:
<insert id="insert">
insert into sys_user(
user_name, user_password, user_email,
user_info, head_img, create_time)
values(
#{userName}, #{userPassword}, #{userEmail},
#{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
</insert>
此处的<insert>中的SQL就是一个简单的INSERT语句,将所有的列都列举出来,在values中通过#{property}的方式参数中取出属性的值。为了防止类型错误,对于一些特殊的数据类型,建议制定具体的jdbcType值。例如,headImg制定BLOB类型,createTime制定TIMESTAMP类型。
- BLOB对应的Java类型是ByteArrayInputStream
- 由于数据库区分date、time、datetime类型,但是Java中一般使用java.util.Date类型。因此为了保证数据类型的正确,需要手动制定日期类型。date、time、datetime对应的JDBC类型分别为Date、Time、Timestamp
- 数据库的datetime类型可以存储DATE(时间部分默认为00:00:00)和TIMESTAMP这两种类型的时间,不能存储TIME类型的时间;date类型可以存储DATE和TIMESTAMP类型的时间;只有time类型才能存储TIME类型的时间
返回主键
上述语句默认返回影响的行数,而不是生成的主键。如果想要返回生成的主键,可以使用如下的两种方法
- 使用JDBC方式返回主键自增的值
- 使用selectKey返回主键的值
使用JDBC方式返回主键自增的值
这种方式适用于能够提供主键自增的数据库中,例如MySQL、SQL Server
直接上代码:
<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
insert into sys_user(
user_name, user_password,
<if test="userEmail != null">
<if test="userEmail != ''">
user_email,
</if>
</if>
user_info, head_img, create_time)
values(
#{userName}, #{userPassword},
<if test="userEmail != null">
<if test="userEmail != ''">
#{userEmail},
</if>
</if>
#{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
</insert>
通过useGeneratedKeys和keyProperty可以实现自增的需求。useGeneratedKeys设置为true,MyBatis使用JDBC的getGeneratedKeys方法获取数据库生成的主键,并将结果赋值给keyProperty指定的对象属性值。当需要获取多个数据库自动生成的值时,使用逗号隔开keyProperty的属性值,并设置keyColumn属性,按顺序指定数据库的列,这里要求keyColumn中列的顺序与keyProperty中属性的顺序一一对应。
使用selectKey返回主键的值
对于不提供主键自增功能的数据库,我们需要先使用序列得到一个值,然后将这个值赋给id,再将数据插入数据库,MyBatis提供了<selectKey>标签来完成该需求。这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
mysql
<insert id="insert3">
insert into sys_user(
user_name, user_password, user_email,
user_info, head_img, create_time)
values(
#{userName}, #{userPassword}, #{userEmail},
#{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
<selectKey keyColumn="id" keyProperty="id" resultType="long" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
oracle
<insert id="insertOracle">
<selectKey keyColumn="id" keyProperty="id" resultType="long" order="BEFORE">
SELECT SEQ_USER.nextval from dual
</selectKey>
insert into sys_user(
id, user_name, user_password, user_email,
user_info, head_img, create_time)
values(
#{id}, #{userName}, #{userPassword}, #{userEmail},
#{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
</insert>
- <selectKey>标签的keyColumn、keyProperty和上面useGeneratedKeys的用法含义相同
- resultType用于设置返回值类型;
- order属性的设置和使用的数据库有关:mysql中,order属性设置为AFTER,因为当前记录的主键值在insert语句执行成功后才能回去到;Oracle中设置为BEFORE,因为Oracle需要先从序列获取值,然后将值作为主键插入到数据库中。
- <selectKey>标签放置的位置不会影响处理的结果,真正有影响的是order属性
下面列举一些在<selectKey>标签中常用的SQL
- Oracle :select SEQ_ID.nextval from dual
- MySQL :select LAST_INSERT_ID()
- DB2 :values IDENTITY_VAL_LOCAL()
- SQL Server :select SCOPE_IDENTITY()
update用法
基本的update用法,比较复杂的update用法是结合<if>标签来更新不为null的值,以及使用<foreach>实现的批量更新,以上两种用法需要使用MyBatis的动态SQL。
<update id="updateById">
update sys_user
set user_name = #{userName},
user_password = #{userPassword},
user_email = #{userEmail},
user_info = #{userInfo},
head_img = #{headImg, jdbcType=BLOB},
create_time = #{createTime, jdbcType=TIMESTAMP}
where id = #{id}
</update>
delete用法
<delete id="deleteById">
delete from sys_user where id = #{id}
</delete>
对应的Mapper.java中的方法可以为:
deleteById(Long id);
// 或
deleteById(SysUser user);
多个接口参数
在实际应用中经常会遇到使用多个参数的情况。一种方法是,将多个参数合并到一个JavaBean中,并使用这个JavaBean作为接口方法的参数。这种方法使用起来很方便,但不适合全部的情况,因为不能只为了两三个参数去创建新的JavaBean。因此,对于参数比较少的情况,还有两种方式可以采用:使用Map类型作为参数;使用@Param注解。
使用Map类型作为参数的方法,就是在Map中通过key来映射XML中SQL使用的参数值名字,value用来存放参数值,需要多个参数时,通过Map的key-value方式传递参数值,由于这种方式还需要自己手动创建Map以及对参数进行赋值,其实并不简洁。所以在实际使用时,当有多个参数时,推荐使用@Param的方式。接下来先介绍不使用@Param注解时,使用多个参数的问题
<select id="selectByUser" 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
where 1 = 1
and user_id = #{userId}
and enabled = #{enabled}
</select>
对应的接口方法为:
SysUser selectByUser(Long userId, String enabled);
调用selectByUser方法时,MyBatis会抛出一下异常:
Parameter 'userId' not found
Available parameters are [0, 1, param1, param2]
这个错误标示,XML可用的参数只有0、1、param1、param2,没有userId。没有0和1,param1和param2都是MyBatis根据参数位置自定义的名字。这时如果将XML中的#{userId}改为#{0}或#{param1},将#{enabled}改为#{1}或#{param2},selectByUser方法就会被正常调用了,在实际使用时并不建议这么做。
接下来介绍使用@Param的情况
修改接口方法为:
SysUser selectByUser(@Param("userId")Long userId, @Param("enabled") String enabled);
这时的XML文件中对应的SQL的可用参数编程了[userId, enabled, param1, param2],如果把#{userId}变成#{param1},#{enabled}变成#{param2}方法也能正常执行。
实际上,给参数配置@Param注解后,MyBatis就会自动将参数封装成Map类型,@Param注解值会作为Map中的key,参数值作为Map中的value。
当只有一个参数的时候,MyBatis不关系这个参数叫什么名字就会直接把这个唯一的参数值拿来使用。
以上内容使用的多个参数都是基本类型或包装类型,当参数是对象类型时,情况略有不同,假设接口方法为
SysUser selectByUser(@Param("user")SysUser user, @Param("role") SysRole role);
则XML中也需要做对应的修改:
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
select id,
u.user_name userName,
u.user_password userPassword,
u.user_email userEmail,
u.user_info userInfo,
u.head_img headImg,
u.create_time createTime
from sys_user u
inner join sys_user_role ur on u.user_id = ur.user_id
inner join sys_role r on ur.role_id = r.role_id
where 1 = 1
and u.user_id = #{user.userId}
and r.enabled = #{role.enabled}
</select>
当参数是对象是,就不能直接使用#{userId}和#{enabled}了,而是要通过点取值方式使用#{user.id}和#{role.enabled}从两个JavaBean中取出指定属性的值。
- [ ] 除了以上常用的参数类型外,接口的参数还可能是集合或数组
动态代理
为什么Mapper接口没有实现类却能被正常调用呢?这是因为MyBatis在Mapper接口上使用了动态代理的机制
假设存在如下Mapper接口:
public interface UserMapper{
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>
使用Java动态代理方式创建一个代理类:
public class MapperProxy<T> implements InvocationHandler {
private Class<T> mapperInterface;
private SqlSession sqlSession;
public MapperProxy(Class<T> mapperInterface, SqlSession sqlSession){
this.mapperInterface = mapperInterface;
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Objectp[] args) throws Throwable{
// 需要代理的接口中的方法
String methodName = mapperInterface.getCanonicalName() + "." + method.getName();
// 将接口中的方法代理到SqlSession中的对应方法
List<T> list = sqlSession.selectList(methodName);
// 返回结果
return list;
}
}
测试代码如下:
// 构建代理类
MapperProxy userMapperProxy = new MapperProxy(UserMapper.class, sqlSession);
// 获取UserMapper接口的代理类
UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{ UserMapper.class },
userMapperProxy
);
// 调用UserMapper接口中的selectAll()方法,实际上被代理到了SqlSession中的方法
List<SysUser> users = userMapper.selectAll();