MyBatis动态SQL
本篇内容:
- 五个标签介绍:if,choose(when、otherwise),trim(where、set)、foreach、bind
- OGNL(Object-Graph Navigation Language)表达式介绍
介绍-if标签
if的主要用处:
- WHERE语句中,通过判断参数值来决定是否使用某个查询条件
- UPDATE/INSERT语句中通过判断是否更新/插入一个字段
实践-WHERE中用if
要求:当只输入用户名时,需要根据用户名进行模糊查询;当只输入邮箱时,根据邮箱进行完全匹配;当同时输入用户名和邮箱时,用前面的两个条件去查询。
mapper接口
/**
* 查询用户列表
* @param user 条件
* @return 用户列表
*/
List<SysUser> selectByUser(SysUser user);
xml定义,使用if标签来根据条件确定查询sql
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
SELECT
id,
user_name,
user_password,
user_email,
user_info,
head_img,
create_time
FROM
sys_user
<where>
<if test="userName !=null and userName!=''">
and user_name like concat('%',#{userName},'%')
</if>
<if test="userEmail !=null and userEmail!=''">
and user_email = #{userEmail}
</if>
</where>
</select>
测试方法:
public void selectByUser() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
//使用用户名加邮箱
SysUser sysUser1 = new SysUser();
sysUser1.setUserEmail("admin");
sysUser1.setUserName("a");
//只用用户名
List<SysUser> sysUsers = userMapper.selectByUser(sysUser1);
LOGGER.info("用户信息:{}",sysUsers.toString());
SysUser sysUser2 = new SysUser();
sysUser2.setUserName("a");
List<SysUser> sysUsers2 = userMapper.selectByUser(sysUser2);
LOGGER.info("用户信息2:{}",sysUsers2.toString());
//只用邮箱
SysUser sysUser3 = new SysUser();
sysUser3.setUserEmail("admin");
// 调用查询方法
List<SysUser> sysUsers3 = userMapper.selectByUser(sysUser3);
LOGGER.info("用户信息3:{}",sysUsers3.toString());
} finally {
sqlSession.close();
}
}
测试结果
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name like concat('%',?,'%') and user_email = ?
==> Parameters: a(String), admin(String)
<== Total: 0
用户信息:[]
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name like concat('%',?,'%')
==> Parameters: a(String)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
用户信息2:[SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}]
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_email = ?
==> Parameters: admin(String)
<== Total: 0
用户信息3:[]
总结:
if的test属性是一个符合OGNL要求的判断表达式,表达式的结果为boolean或者非0表示true,0为false。
- 判断条件property!=null或property==null:适用于任何类型的字段,用于判断属性值是否为空
- 判断条件property!=’ ‘或property==’ ':仅适用于String类型的字段,用于判断是否为空字符串
- and和or:多个判断条件使用and或or连接,嵌套的判断使用小括号分组
实践-UPDATE中用if
要求:只更新有变化的字段,更新时不能将原值未变的字段更新为空。
mapper接口
/**
* 根据主键更新
* @param user 条件
* @return 影响的行数
*/
int updateByIdSelective(SysUser user);
xml,注意我们最后一个if标签的sql结尾带了逗号,这样的写法在set标签中也是可以执行的
<update id="updateByIdSelective">
update mybatis.sys_user
<set>
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="userPassword != null and userPassword != ''">
user_password = #{userPassword},
</if>
<if test="userEmail != null and userEmail != ''">
user_email = #{userEmail},
</if>
<if test="userInfo != null and userInfo != ''">
user_info = #{userInfo},
</if>
<if test="headImg != null">
head_img = #{headImg,jdbcType=BLOB},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id}
</update>
测试方法
public void updateByIdSelective() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
//获取数据库中一条记录进行修改
SysUser user = new SysUser();
user.setId(1L);
// 修改用户名
user.setUserName("admin_test");
user.setUserEmail("test@mybatis.tk");
//更新数据,update方法返回值是执行sql影响的行数
int result = mapper.updateByIdSelective(user);
Assert.assertEquals(1,result);
//查看修改后的数据
SysUser sysUser = mapper.queryById(1L);
LOGGER.info("修改后的用户信息:{}",sysUser.toString());
Assert.assertEquals("admin_test",sysUser.getUserName());
} finally {
//为了不影响其他测试,这里进行回滚
//由于默认的sqlSessionFactory.openSession()是不自动提交的
//因此不手动执行commit也不会提交到数据库
sqlSession.rollback();
//关闭sqlSession
sqlSession.close();
}
}
测试结果
==> Preparing: update mybatis.sys_user SET user_name = ?, user_email = ? where id = ?
==> Parameters: admin_test(String), test@mybatis.tk(String), 1(Long)
<== Updates: 1
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin_test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
修改后的用户信息:SysUser{id=1, userName='admin_test', userPassword='123456', userEmail='test@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
实践-INSERT中用if
要求:实现动态插入列功能。
修改用户表邮箱字段:
alter table sys_user
modify column user_email varchar(50) null default 'test@mybatis.tk' comment '邮箱'
after user_password;
mapper接口
/**
* 插入一条记录
* @param user 条件
* @return 影响的行数
*/
int insert4(SysUser user);
xml内容
<insert id="insert4" useGeneratedKeys="true" keyProperty="id">
insert into `sys_user`(
user_name,
user_password,
<if test="userEmail !=null and userEmail != ''">
user_email,
</if>
user_info,
head_img,
create_time
)
values (
#{userName},
#{userPassword},
<if test="userEmail !=null and userEmail != ''">
#{userEmail},
</if>
#{userInfo},
#{headImg,jdbcType=BLOB},
#{createTime,jdbcType=TIMESTAMP}
)
</insert>
测试方法
public void insert4() {
SqlSession sqlSession = getSqlSession();
try {
// 初始化一个sysUser对象
SysUser sysUser = new SysUser();
sysUser.setUserName("test1");
sysUser.setUserPassword("123456");
sysUser.setUserInfo("test info");
sysUser.setHeadImg(new byte[]{1,2,3});
sysUser.setCreateTime(new Date());
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
//将新建的对象插入数据库中,这里的返回值是执行的SQL影响的行数
int insert = mapper.insert4(sysUser);
//只插入一条记录
Assert.assertEquals(1,insert);
LOGGER.info("插入的记录:{}",sysUser.toString());
SysUser sysUser1 = mapper.queryById(sysUser.getId());
LOGGER.info("插入数据库的内容:{}",sysUser1.toString());
} finally {
//由于默认的sqlSessionFactory.openSession()是不自动提交的,因此不手动执行commit也不会提交到数据库
sqlSession.rollback();
// sqlSession.commit();
//关闭sqlSession
sqlSession.close();
}
}
测试结果:
==> Preparing: insert into `sys_user`( user_name, user_password, user_info, head_img, create_time ) values ( ?, ?, ?, ?, ? )
==> Parameters: test1(String), 123456(String), test info(String), java.io.ByteArrayInputStream@64c63c79(ByteArrayInputStream), 2021-03-18 16:42:34.822(Timestamp)
<== Updates: 1
插入的记录:SysUser{id=1003, userName='test1', userPassword='123456', userEmail='null', userInfo='test info', headImg=[1, 2, 3], createTime=Thu Mar 18 16:42:34 CST 2021}
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ?
==> Parameters: 1003(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1003, test1, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-03-18 16:42:35.0
<== Total: 1
插入数据库的内容:SysUser{id=1003, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='test info', headImg=[1, 2, 3], createTime=Thu Mar 18 16:42:35 CST 2021}
介绍-choose标签
if标签无法实现if…else、if…else的逻辑。choose when otherwise可以。
choose元素中包含when和otherwise两个标签,一个choose中至少有一个when,至多有1个otherwise。
实践
要求:默认用户名不能重复。查询用户,当参数id有值使用id查询;当id无值,判断用户名是否有值,有值就用用户名查询,如果用户名也无值,就使sql查询无结果。
mapper接口
/**
* 根据用户id或用户名查询
* @param user 条件
* @return 用户数据
*/
SysUser selectByIdOrUserName(SysUser user);
xml
<select id="selectByIdOrUserName" resultType="tk.mybatis.simple.model.SysUser">
SELECT
id,
user_name,
user_password,
user_email,
user_info,
head_img,
create_time
FROM
sys_user
<where>
<choose>
<when test="id != null">
and id =#{id}
</when>
<when test="userName !=null and userName !=''">
and user_name=#{userName}
</when>
<otherwise>
and 1=2
</otherwise>
</choose>
</where>
</select>
测试方法
public void selectByIdOrUserName() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
//使用id
SysUser userCondition = new SysUser();
userCondition.setId(1L);
//只用用户名
SysUser sysUser = userMapper.selectByIdOrUserName(userCondition);
LOGGER.info("用户信息:{}",sysUser.toString());
//使用用户名
SysUser userCondition2 = new SysUser();
userCondition2.setUserName("admin");
//只用用户名
SysUser sysUser2 = userMapper.selectByIdOrUserName(userCondition2);
LOGGER.info("用户信息:{}",sysUser2.toString());
//使用用户名
SysUser userCondition3 = new SysUser();
userCondition3.setUserEmail("admin");
//只用用户名
SysUser sysUser3 = userMapper.selectByIdOrUserName(userCondition3);
LOGGER.info("用户信息:{}",Optional.ofNullable(sysUser3).map(SysUser::toString).orElse("null"));
} finally {
sqlSession.close();
}
}
测试结果
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name=?
==> Parameters: admin(String)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE 1=2
==> Parameters:
<== Total: 0
用户信息:null
总结:如果没有otherwise这个限制条件,所有的用户都会被查询出来;而接口返回值类型为SysUser,当查出多条记录时,会报错。
otherwise可搭配update使用,避免更新时无条件全部更新。
介绍-where标签
where和set属于trim的一种具体用法。
where标签的作用:
- 如果标签中有返回值,就插入一个where
- 如果where后面的字符串是以and和or(不区分大小写)开头的,就将他们剔除
介绍-set标签
set标签的作用:
- 如果该标签的元素中有返回值,就插入一个set
- 如果set后面的字符串是以逗号结尾,就将逗号剔除
总结:
在set标签中,最后的逗号问题解决了;当if标签内容都为空时,为了避免出错,需要加一条更新语句:id=#{id}
有必要。
例如:
<update id="updateByIdSelective">
update mybatis.sys_user
<set>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
id = #{id},
</set>
where id = #{id}
</update>
介绍-trim用法
where和set标签得功能都可以用trim标签来实现。
where标签对应trim的实现如下:
<trim prefix='WHERE' prefixOverrides="AND |OR ">
...
</trim>
这里AND和OR后面的空格是为了避免匹配到andes、orders等单词。
set标签对应的trim实现如下:
<trim prefix='SET' suffixOverrides=",">
...
</trim>
trim标签有如下属性:
- prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀
- prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉
- suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀
- suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉
介绍-foreach用法
使用IN关键字,可以使用${ids}
方式直接获取值,但这种写法不能防止SQL注入,想要避免SQL注入就得用#{}
方式,这时就需要配合使用foreach标签。
foreach可以对数组、Map或实现了Iterable接口(如List、Set)得对象进行遍历。数组在处理时会转化为List对象。
foreach遍历的对象可以分为两大类:Iterable类型和Map类型。这两种类型在遍历循环时情况不一样。
foreach包含以下属性:
- collection:被循环的属性名
- item:变量名,从循环对象中取出的每一个值
- index:索引的属性名,在集合数组情况下值为当前索引值,当迭代循环的对象是map类型时,这个值为map的key
- open:整个循环内容开头的字符串
- close:整个循环内容结尾的字符串
- separator:每次循环的分隔符
MyBatis如何处理参数:
查询-只有一个数组参数或集合参数
下面的是默认情况下的处理逻辑:
当参数类型为集合的时候,默认会转化为Map类型,并添加一个key为collection的值(MyBatis3.3.0),如果参数类型是List集合,那么就继续添加一个key为list的值(MyBatis3.2.8及以下版本),这样,当collection=“list”时,就能得到这个集合,并对它进行循环操作。
当参数类型为数组时,也会转化为Map类型,默认的key为array。
list和array是参数默认的名字,使用@Param
来指定参数的名字,这时collection就设置为通过@Param
指定的名字。
private Object wrapCollection(final Object object){
if(object instanceof Collection){
StrictMap<Object> map=new StrictMap<Object>();
map.put("collection",object);
if(object instanceof List){
map.put("list",object);
}
return map;
}else if(object !=null && object.getClass().isArray()){
StrictMap<Object> map=new StrictMap<Object>();
map.put("array",object);
return map;
}
return object;
}
查询-有多个参数
当有多个参数时,需要使用@Param
注解给每一个参数指定一个名字。
查询-参数是Map类型
使用Map和使用@Param
注解方式类似,将collection指定为对应Map中的key即可。如果要循环所传入的Map,推荐使用@Param
注解指定名字,此时可以将collection设置为指定的名字,如果不想指定名字,就使用默认值_parameter
查询-参数是一个对象
这种情况下指定为对象的属性名。当使用对象内多层嵌套的对象时,使用属性.属性(集合和数组可以使用下标取值)的方式指定深层的属性值。
实践-foreach实现in集合
要求:根据用户id的集合来查询用户,如果传入空集合则返回的结果也要为空。
mapper
/**
* 通过id查询用户列表
* @param list id集合
* @return 用户列表
*/
List<SysUser> selectByIdList(List<Long> list);
xml
<select id="selectByIdList" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name,
user_password,
user_email,
user_info,
head_img,
create_time
from `sys_user`
<where>
<choose>
<when test="list !=null and list.size()>0">
id in
<foreach collection="list" item="id" index="id" open="(" separator="," close=")">
#{id,jdbcType=BIGINT}
</foreach>
</when>
<otherwise>
1=2
</otherwise>
</choose>
</where>
</select>
测试方法:
public void selectByIdList() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
List<Long> idList = new ArrayList<>();
idList.add(1L);
idList.add(2L);
List<SysUser> sysUserList = mapper.selectByIdList(idList);
LOGGER.info("用户信息:{}",sysUserList.toString());
// 查询一个空集合
List<Long> idList2 = new ArrayList<>();
List<SysUser> sysUserList2 = mapper.selectByIdList(idList2);
LOGGER.info("用户信息:{}",sysUserList2.toString());
} finally {
sqlSession.close();
}
}
测试结果,符合我们的要求
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from `sys_user` WHERE id in ( ? , ? )
==> Parameters: 1(Long), 2(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
用户信息:[SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}]
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from `sys_user` WHERE 1=2
==> Parameters:
<== Total: 0
用户信息:[]
实践-foreach实现批量插入
数据库支持批量插入,可以使用foreach来实现。批量插入语法如下:
INSERT INTO tablename(column-a,[column-b,...])
VALUES('value-1a',[‘value-1b’,...]),
('value-2a',[‘value-2b’,...]),
...
mapper接口
/**
* 插入用户记录
* @param userList 用户集合
* @return 影响的行数
*/
int insertList(List<SysUser> userList);
xml,注意foreach标签这里只用了separator属性,因为批量插入的sql无需在values后再额外跟括号
<insert id="insertList">
insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
values
<foreach collection="list" item="user" separator=",">
(
#{user.id},
#{user.userName},
#{user.userPassword},
#{user.userEmail},
#{user.userInfo},
#{user.headImg},
#{user.createTime}
)
</foreach>
<selectKey keyColumn="id" keyProperty="id" resultType="long" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
</insert>
测试类
public void insertList() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
//创建一个user对象
List<SysUser> sysUsers = new ArrayList<>();
for (int i = 0; i < 2; i++) {
SysUser sysUser = new SysUser();
sysUser.setUserName("test"+i);
sysUser.setUserPassword("123456");
sysUser.setUserEmail("test@mybatis.tk");
sysUsers.add(sysUser);
}
//将新建的对象批量插入数据库
//返回值时执行sql影响的行数
int i = mapper.insertList(sysUsers);
LOGGER.info("本次影响的行数:{}", i);
LOGGER.info("插入的记录:{}",sysUsers.toString());
} finally {
//回滚
sqlSession.rollback();
//关闭sqlSession
sqlSession.close();
}
}
测试结果,发现通过select LAST_INSERT_ID()
方法能拿到最新记录的主键,但是无法赋值给集合里的对象。
==> Preparing: insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time) values ( ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: null, test0(String), 123456(String), test@mybatis.tk(String), null, null, null, null, test1(String), 123456(String), test@mybatis.tk(String), null, null, null
<== Updates: 2
==> Preparing: select LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 1025
<== Total: 1
本次影响的行数:2
插入的记录:[SysUser{id=null, userName='test0', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}, SysUser{id=null, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}]
如果需要实现批量插入返回主键给插入记录,需要修改xml为如下,其中只用了useGeneratedKeys和keyProperty属性,含义及取值与单行插入返回主键一致;但是需要升级MyBatis版本为至少3.3.1,否则报错### Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [collection, list]
<insert id="insertList" useGeneratedKeys="true" keyProperty="id">
insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
values
<foreach collection="list" item="user" separator=",">
(
#{user.id},
#{user.userName},
#{user.userPassword},
#{user.userEmail},
#{user.userInfo},
#{user.headImg},
#{user.createTime}
)
</foreach>
</insert>
将pom中mybatis版本升级为3.3.1后,并修改xml后,继续执行测试方法,测试结果如下:发现我们已经能够在插入之后返回主键给插入集合的元素了。
==> Preparing: insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time) values ( ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: null, test0(String), 123456(String), test@mybatis.tk(String), null, null, null, null, test1(String), 123456(String), test@mybatis.tk(String), null, null, null
<== Updates: 2
本次影响的行数:2
插入的记录:[SysUser{id=1037, userName='test0', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}, SysUser{id=1038, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}]
实践-foreach实现动态UPDATE
当参数为Map类型时,foreach如何实现动态UPDATE。
当参数是Map类型时,foreach标签的index属性值对应的不是索引值,而是Map中的key,利用这个key可以实现动态UPDATE。
mapper接口
/**
* 通过map更新列
* @param map 以表字段为key,值为value
* @return 影响的行数
*/
int updateByMap(Map<String,Object> map);
xml,没有使用@Param
注解指定参数名,mybatis使用了默认的_parameter
作为该参数的可以。
<update id="updateByMap">
update sys_user
set
<foreach collection="_parameter" item="val" index="key"
separator=",">
${key}=#{val}
</foreach>
where id = #{id}
</update>
测试方法:
public void updateByMap() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
SysUser sysUser1 = mapper.queryById(1L);
LOGGER.info("更新前的用户数据{}",sysUser1.toString());
Map<String, Object> map = new HashMap<>();
// 查询条件,同样也是更新字段,必须保证该值存在
map.put("id",1L);
// 要更新的字段
map.put("user_email","test@mybatis.tk");
map.put("user_password","123456789");
// 更新数据
mapper.updateByMap(map);
// 根据当前id查询修改后的数据
SysUser sysUser = mapper.queryById(1L);
LOGGER.info("更新后的用户数据{}",sysUser.toString());
} finally {
sqlSession.rollback();
sqlSession.close();
}
}
执行结果:能发现我们update语句只更新了三个字段,并且也会更新我们的主键字段。这就说明我们在map中设置主键值时,key必须为表主键列字段名。
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
更新前的用户数据SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==> Preparing: update sys_user set user_email=? , user_password=? , id=? where id = ?
==> Parameters: test@mybatis.tk(String), 123456789(String), 1(Long), 1(Long)
<== Updates: 1
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456789, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
更新后的用户数据SysUser{id=1, userName='admin', userPassword='123456789', userEmail='test@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
介绍-bind用法
bind标签常用于构造sql中的模糊查询,在实践-WHERE中用if
我们用的是and user_name like concat('%',#{userName},'%')
,使用了concat函数连接字符串,在MySQL中,这个函数支持多个参数,但是在Oracle中只支持两个参数。如果更换数据库,上面的concat写法就得重写了。
可以使用bind标签避免上面更换数据库而需要重写sql。将上面的语句修改为
<if test="userName !=null and userName!=''">
<bind name="userNameLike" value="'%'+userName+'%'"/>
and user_name like #{userNameLike}
</if>
介绍-多数据库支持
bind标签只是解决了模糊查询的问题,别的问题可以使用if标签提供的databaseIdProvider数据库厂商标识配置。
MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。如果同时找到带有databaseId和不带databseId的相同语句,则后者被舍弃。
如需支持多厂商,需要在mybatis-config.xml中添加databaseIdProvider配置。name是数据库产品名,value是我们自定义别名。数据库产品名由JDBC驱动决定,只要找到对应数据库DatabaseMetaData接口实现类,一般在getDatabaseProductName()方法中可以直接找到该值。任何情况下都可以通过调用DatabaseMetaData#getDatabaseProductName()方法获取具体的值。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"
</databaseIdProvider>
除了增加上面的配置外,映射文件中修改if标签配合默认上下文中_databseId
参数这种写法可以实现,这样可以避免大量重复的SQL出现。
介绍-OGNL语法
Mybatis中动态SQL和$ {}形式的参数中都用到了OGNL表达式,所以我们有必要了解一下OGNL的简单用法。
Mybatis常用的OGNL表达式如下:
- e1 or e2
- e1 and e2
- e1 == e2 或 e1 eq e2
- e1 != e2或e1 neq e2
- e1 lt e2 小于
- e1 lte e2 小于等于
- e1 gt e2 大于
- e1 gte e2 大于等于
- e1 + e2
- e1 * e2
- e1/e2
- e1 - e2
- e1 % e2
- !e 或 not e :非,取反
- e.method(args):调用对象方法
- e.property: 对象属性值
- e1 [e2] : 按索引取值(List、数组、Map)
- @Class@method(args):调用类的静态方法
- @Class@field:调用类的静态字段值
实践-判断集合是否为空
当我们需要判断一个集合是否为空时,可以用如下两种判断方式
<if test="@org.apache.commons.collections.CollectionUtils@isNotEmpty(applyerIds)">
heu.employee_id IN
<foreach collection="applyerIds" item="id" index="index"
open="(" separator="," close=")">
#{id}
</foreach>
</if>
<if test="applyerIds !=null and applyerIds.size()>0)">
heu.employee_id IN
<foreach collection="applyerIds" item="id" index="index"
open="(" separator="," close=")">
#{id}
</foreach>
</if>
在上面的这种用法中,applyerIds.size()是调用对象的方法,>0是和数字进行比较。
实践-获取对象的属性
e.property及e1 [e2]也经常用到,而且都可以多层嵌套使用。
假设User类的对象user有一个Address类型的属性addr,Address中还有一个属性zipcode,在mybatis中可以通过user.addr.zipcode直接使用zipcode的值。
假设Map类型的属性未map,我们可以通过map[‘userName’]或map.userName来获取map中key为userName的值,一定要注意,不管userName的值是不是null,必须保证userName这个key存在,否则会报错。
@Class@method(args)通常用于简化一些校验,或者进行更特殊的校验。
实践-灵活使用OGNL表达式,获取用户信息
下面三行是h0系统中获取当前登录员工id的操作。1和2行是用来获取登录用户的用户id和tenantId,第三行是用来获取当前登录员工id的,需要注意的是,在bind标签中使用bind标签的值无需加#{}符。当在where条件中使用时会需要加#{}符号
<bind name="currentUserId" value="@io.choerodon.core.oauth.DetailsHelper@getUserDetails().userId"/>
<bind name="currentTenantId" value="@io.choerodon.core.oauth.DetailsHelper@getUserDetails().tenantId"/>
<bind name="currentEmployeeId" value="@org.hzero.boot.platform.plugin.hr.EmployeeHelper@getEmployee(currentUserId,currentTenantId).employeeId"/>