06-Mybatis中的传参、属性和动态SQL
1、mybatis中参数传递
1.1 MyBatis中对sql语句参数的传递
这里要明白一点,由于mybatis是在dao层起作用,dao层的接口中定义了和一个sql映射文件中各条sql语句的方法,
就是说接口中的方法中需要传递的参数都是与sql语句中需要的参数对应的,
那么映射文件中每个sql语句中添加parameterType属性以指定传递参数是什么类型就变得可有可无了。
1、--一个参数的传递
这类情况就是mapper文件中的sql语句只需要一个参数,如按表中id查询数据。
传递一个参数一般都是传递简单数据类型,dao接口中的方法一般如下:
public User selectUserById(Integer id);
映射文件中的sql语句的占位符中变量名可以随便写:select * from t_user where id=#{随便写}
当我们用接口实现类对象调用selectUserById时,动态代理生成的实现类中调用SqlSession对象的selectOne方法,
将整型参数传进去,因为sql语句中只需要一个参数,这个值怎么传都会传给sql中的参数位置,
所以在占位符中特意指定接收属性的参数值。
2、--多参数传递
这类情况就是mapper文件中的sql语句需要多个参数,
如按表中id和(或)name查询数据,或者是insert操作,亦或是update操作等等。
多参传递有多种方式:
--1)注解@Param命名参数传参--(掌握)
@Param("参数名") 数据类型 变量名
dao接口方法中参数定义如下:
public List<User> selectByNameOrId(@Param("loginName") String name,@Param("id") Integer id);
当调用该方法时内部是将我们输入的name(id)参数的值以注解中定义的参数名为key存储到一个集合中(这只是猜测),
所以sql语句中的占位符中填写正确的对应参数名#{}
--当然sql中的条件一般都是表中的字段,所以参数名都是按表中对应字段命名。
--2)JavaBean对象传参
dao接口方法中参数定义如下:
public List<User> addUser(User user);
例如作添加数据操作:
需要创建JavaBean对象,然后给该对象相关属性赋值,最后作为参数传递给到、接口的方法中
映射文件中接收参数的占位符中填参数名就是User对象中各个属性名,为了开发方便,
就得使这个实体类中JavaBean的字段名和表中的字段名一致
--3)按位置传参
dao接口方法中参数根据前后进行排序,第一个是0,第二个是1.
直接调用dao接口方法时将参数写到方法中,就完成传参操作了。
这种方式不需要我们定义0位置参数的名字,mybatis已经定义好了。mybatis3.3版本以及之前版本是0,代表第一个
3.4及以后用arg0表示第一个参数。很明显mybatis把接口方法中的参数放进了一个可变长度的参数数组,而sql语句中
的占位符可以根据下标0、1、2...来接收参数,只不过下标前面加了arg。
select * from t_user where id=#{arg0} or loginName=#{arg1}
只不过这种方式不知道具体arg0代表的是什么意思的值,容易搞错。
--4)使用集合Map传值
public List<User> selectByMap(Map<String,Object>);
首先得创建Map集合,往集合中存储数据put("表中字段名",value),集合key命名为对应表中的字段名,
调用dao接口方法时把Map集合传进去。
sql语句中的占位符中填写的是map集合中的key,所以key的命名与表中一致可以很便捷。
--这四种方法用的最多的是前两种方法,在需要传递的参数涉及两张表时,首选@Param,其次是Map方式
1.2 占位符#和$的区别
mybatis映射文件中sql语句参数使用"#{参数名}"来代替JDBC中的"?"占位符,其实mybatis还有一种占位符是"${参数名}"
我们知道,JDBC有两种获取数据库操作对象的方式:
"PrepareStatement"和"Statement"
前者是将sql语句先编译,没有值的位置用"?"这个占位符来代替,后期通过
PrepareStatement对象的setInt(整数值)(或setString(字符串)等等)方法给"?"赋值,然后执行sql语句。
后者则是让sql语句进行按需求进行添加字符串的拼接,然后执行sql语句,有sql注入风险。
而mapper文件中sql语句里的占位符用#{}的话,底层执行时,使用的是PrepareStatement对象进行的,
所以这个#{}是mybatis用来代替?的。
如果mapper文件中sql语句里的占位符用${},$表示告诉mybatis使用$表示的字符串替换这条sql语句其所在位置。
主要用来替换表名列名,不同序列等操作。
1.3 测试根据id查询t_user表中的数据,通过输出执行日志来比较使用#或者是$占位符的区别
select * from t_user where id=#{id}
日志输出如下:
Opening JDBC Connection
Created connection 230643635.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dbf57b3]
==> Preparing: select * from t_user where id=?
==> Parameters: 1(Integer)
<== Columns: id, loginName, loginPwd, realName
<== Row: 1, xxpiaozhiyan, yanyan, 朴智妍
<== Total: 1
User{id=1, loginName='xxpiaozhiyan', loginPwd='yanyan', realName='朴智妍'}
--所以用#得到的是:select * from t_user where id=? 然后给?赋值
select * from t_user where id=${id}
日志输出如下:
Opening JDBC Connection
Created connection 1388278453.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@52bf72b5]
==> Preparing: select * from t_user where id=1
==> Parameters:
<== Columns: id, loginName, loginPwd, realName
<== Row: 1, xxpiaozhiyan, yanyan, 朴智妍
<== Total: 1
User{id=1, loginName='xxpiaozhiyan', loginPwd='yanyan', realName='朴智妍'}
--所以用$得到的是:select * from t_user where id=1 然后直接运行
2、mybatis中resultMap属性
2.1 补充前面对结果集的处理
前面学习过使用JavaBean对象来存储查询结果集,这是针对查询一张表的情况,在resultType中填写JavaBean的完整类名。
--这种方式还有一个要求就是:
查询结果的列名要和JavaBean对象的属性名一致。要保持这种一致性有两种方法:
1)javabean类的字段名与要查询的表中的字段名全部一致
2)在查询语句中将查询结果的列名起别名,该别名和JavaBean的字段名一致,达到传递数据结果到JavaBean对象的要求
--除了以上以外,如果JavaBean类中的字段名与表中的不一致,结果集列名又不起别名的情况下:
使用<resultMap>标签进行"结果映射",--指定结果集列名和java对象属性的对应关系
该标签是与sql语句标签平级的,在<select>等标签之前设置,有自己的唯一标识id,而且设置对应关系时列名如果是主键的值与其他普通列名的指定是由区别的。案例如下:
2.2 使用resultMap属性实现结果映射
我的实体类如下User.java,一定要确保实体类中有无参数的构造方法,因为mybatis的动态代理是用实体类的无参构造来自动创建实体类对象的
package com.mybatis.domain;
public class User {
private int id;
private String account;
private String password;
private String name;
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", account='" + account + '\'' +
", password='" + password + '\'' +
", name='" + name + '\'' +
'}';
}
}
sql映射文件如下
<?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="test">
<!--
注意:parameterType属性是给sql语句中占位符传值的,定义占位符传什么类型对象的属性的值到该位置上
翻译为:参数类型,下面insert语句中传递的是com.mybatis.domain.User类的属性字段值
同时javabean在给占位符传值时,程序员要告诉mybatis应该将javabean的哪个属性字段值传到哪个占位符上
所以占位符中需要有javabean的属性字段名
mybatis中占位符不用问号表示,因为要放javabean的字段信息,所以用:#{这里写javabean的属性字段名} 表示
#{loginName}底层原理是mybatis会将#{}中的属性字段名获取到,将其首字母大写,然后在前面添加get,调用
javabean中的各字段的get方法来获取到值,从而组成sql语句执行
-->
<!--定义resultMap
id自己定义,代表你定义的这个resultMap
type:填对应Java类型的全限定名-->
<resultMap id="user" type="com.mybatis.domain.User">
<!--对主键结果列映射的设置
使用id标签
column:填查询结果的列名
property:Java类型的属性名
-->
<id column="id" property="id"/>
<!--非主键列
使用result标签-->
<result column="loginName" property="account"/>
<result column="loginPwd" property="password"/>
<result column="realName" property="name"/>
</resultMap>
<!--使用上面设置的映射关系,直接将某个resultMap的id放进resultType属性中-->
<select id="getById" resultType="user">
select
id,loginName,loginPwd,realName
from
t_user
where
id = #{id}
</select>
<select id="getAll" resultType="user">
select
*
from
t_user
</select>
</mapper>
这种方式主要是将来不得不改表中的字段名,由于项目中字段名一般都是非常多的,修改java类麻烦,而sql语句起别名也麻烦,因为sql语句可能有很多,这样我们就可以使用resultMap属性设置映射关系。
--一般情况不用这种
3、mybatis中的动态SQL
3.1 动态sql的概念
--动态sql:
就是说sql语句是动态变化的,映射文件中的sql语句根据条件让mybatis获取不同的sql语句
主要是where的部分进行动态化
--实现动态sql
通过使用mybatis中的标签来实现sql语句的动态化,标签有<if>、<where>、<foreach>
3.2 标签,是判断条件的
语法如下:
<if test="判断java对象的属性值">
待拼接的sql语句
</if>
在idea-maven-mybatis工程中添加新模块进行测试,模块项目名为:d-maven-dynamic-sql-mybatis04
<!--注意标签<if>的动态sql语句中接口方法中的参数一般是Java对象-->
3.3 测试标签
UserDao接口方法如下:
package com.studymyself.dao;
import com.studymyself.entity.User;
import java.util.List;
//接口操作t_user表
public interface UserDao {
//通过if动态sql语句查询数据,参数必须是Java对象
public List<User> selectByIf(User user);
}
sql映射文件如下:
<?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="com.studymyself.dao.UserDao">
<select id="selectByIf" resultType="User">
select id,loginName,loginPwd,realName from t_user
where
<!--上面表示主语句-->
<!--if表示这个标签中传递过来的Java对象的id字段值大于0
就将if标签中的sql语句块拼接到主语句中去-->
<if test="id > 0">
id=#{id}
</if>
<!--传过来的java对象中的realName值不为空或不是空字符串
就将if标签中的sql语句块拼接到主语句中去-->
<if test="loginName!= null or loginName !='' ">
or realName = #{realName}
</if>
</select>
</mapper>
测试程序如下
@Test
public void testSelectByIf(){
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//通过SqlSession对象中的方法获取实现类对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = new User();
user.setId(1);
user.setRealName("钟荣杰");
List<User> users = userDao.selectByIf(user);
for (User user1:
users) {
System.out.println(user1);
}
}
从上面的语句中我们可以知道有这样的情况:
第一个if标签中条件不成立,但第二个条件成立将sql语句块添加到主语句时会拼接成错误的sql语句块:
select id,loginName,loginPwd,realName from t_user where or realName = ?
避免这种情况,我们可以在where后添加一个 1=1 的条件,然后第一个if标签中的语句块前面都加连接关键字or and
<select id="selectByIf" resultType="User">
select id,loginName,loginPwd,realName from t_user
where 1=1
<if test="id > 0">
or id=#{id}
</if>
<if test="realName!= null or realName !='' ">
or realName = #{realName}
</if>
</select>
上面在where后边添加恒等条件的方式可以用
标签解决
3.3 标签
<where>标签用来代替sql主语句中where关键的位置,用来包含多个if标签,当多个if只要有一个成立,该标签就自动生成一个where关键字,还会将第一个拼接到sql主语句的if标签的sql语句块的or、and连接关键字去掉。
3.4 测试标签
在dao接口中添加方法,如下
package com.studymyself.dao;
import com.studymyself.entity.User;
import java.util.List;
//接口操作t_user表
public interface UserDao {
//通过if动态sql语句查询数据,参数必须是Java对象
public List<User> selectByIf(User user);
//通过if和where动态sql语句查询数据,参数必须是Java对象
public List<User> selectByWhere(User user);
}
sql映射文件如下
<?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="com.studymyself.dao.UserDao">
<select id="selectByIf" resultType="User">
select id,loginName,loginPwd,realName from t_user
where
<!--上面表示主语句-->
<!--if表示这个标签中传递过来的Java对象的id字段值大于0
就将if标签中的sql语句块拼接到主语句末端中去-->
<if test="id > 0">
id=#{id}
</if>
<!--传过来的java对象中的realName值不为空或不是空字符串
就将if标签中的sql语句块拼接到主语句末端中去-->
<if test="realName!= null or realName !='' ">
or realName = #{realName}
</if>
</select>
<select id="selectByWhere" resultType="User">
select id,loginName,loginPwd,realName from t_user
<where>
<if test="id > 0">
or id=#{id}
</if>
<if test="realName!= null or realName !='' ">
or realName = #{realName}
</if>
</where>
</select>
</mapper>
测试程序如下
@Test
public void testSelectByWhere(){
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//通过SqlSession对象中的方法获取实现类对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = new User();
user.setId(1);
user.setRealName("钟荣杰");
List<User> users = userDao.selectByWhere(user);
for (User user1:
users) {
System.out.println(user1);
}
}
上面测试程序的执行日志如下
Opening JDBC Connection
Created connection 2114444063.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7e07db1f]
==> Preparing: select id,loginName,loginPwd,realName from t_user WHERE id=? or realName = ?
==> Parameters: 1(Integer), 钟荣杰(String)
<== Columns: id, loginName, loginPwd, realName
<== Row: 1, xxpiaozhiyan, yanyan, 朴智妍
<== Row: 3, 16020520009, 123456789, 钟荣杰
<== Total: 2
User{id=1, loginName='xxpiaozhiyan', loginPwd='yanyan', realName='朴智妍'}
User{id=3, loginName='16020520009', loginPwd='123456789', realName='钟荣杰'}
从日志可以看到,条件where是个大写的生成的,还把or去掉了,两个if语句不成立的话就是一个无条件的查询语句。
3.5 标签
当我们要执行的是带有in条件的sql语句时,in中的值是多个的,
--当接口方法中传的参数是一个Map集合,我们可以用平常使用的方法把需要传参用#{}来代替,其中括号中的属性名是集合的key值,创建map集合,给集合添加数据,然后调用接口方法把map集合传进去。
--当接口中的参数是List集合时,我们创建List集合,给集合添加数据,然后调用接口方法把List集合传进去,由于List集合不是键值对存储的,传进去后没法通过特定属性名用#{}取值。
上面的第二种方式在用<foreach>标签后可以将List集合中的值存放到in条件中,原理如下面代码所示:
//下面代码演示<foreach>对传入的List集合处理的内部机理
//这里新建List集合
List<Integer> list = new ArrayList<>();
//添加数据
list.add(1);
list.add(4);
list.add(7);
//假设映射文件中sql主语句,需要添加in后面的条件
String sql = "select id,loginName,loginPwd,realName from t_user where id in";
//需要的条件是这样的:(1,7,4)
//创建一个字符串拼接类StringBuffer
StringBuffer buffer = new StringBuffer();
//添加字符(
buffer.append("(");
//遍历list集合,并且追加到buffer中,中间记住加,
for (Integer i:
list) {
buffer.append(i).append(",");
}
//除掉倒数第二个逗号
buffer.deleteCharAt(buffer.length()-1);
//追加最后一个)
buffer.append(")");
//与主语句拼接
sql = sql + buffer;
System.out.println(sql);//select id,loginName,loginPwd,realName from t_user where id in(1,4,7)
<foreach>就是用来循环java中数组和list集合的。主要用在in语句中,使用规范如下
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
collection:表示接口中方法的参数类型,数组参数填array,集合参数填list
item:这个是自定义的变量,表示数组或集合成员的元素
open:循环开始时的字符,如上面代码中的"("
close:循环结束时的字符,如上面代码中的")"
separator:集合或数组中元素拼接时之间的分隔符。如逗号
<!--有时候我们不一定要把所有的参数都写,如下-->
<!--当我们把foreach标签放到()中,open和close就不用写了-->
<select id="selectByForeach" resultType="User">
select id,loginName,loginPwd,realName from t_user where id in(
<foreach ollection="list" item="id" separator=",">
#{id}
</foreach>
)
</select>
<!--有时候我们不一定要把所有的参数都写,如下-->
<!--当我们把foreach标签放到()中,逗号放到#{}后面,open和close以及separator就不用写了
但是需要在foreach标签后添加一个不影响查询条件的值,因为拼接后sql语句的in条件多出一个,
结果: select id,loginName,loginPwd,realName from t_user where id in(?,?,?,0)
-->
<select id="selectByForeach" resultType="User">
select id,loginName,loginPwd,realName from t_user where id in(
<foreach ollection="list" item="id">
#{id},
</foreach>
0
)
</select>
<!--有时候list集合中的参数不一定是简单类型,当是java对象时,如下操作拿到对象中字段的值进行拼接-->
<select id="selectByForeach" resultType="User">
select id,loginName,loginPwd,realName from t_user where id in
<foreach ollection="list" item="user" open="(" close=")" separator=",">
#{user.id}
</foreach>
</select>
3.6 参数非javabean类型时如何使用其中的参数
1)使用arg0 , arg1来表示接口方法中的第一个和第二个位置的参数
例如 :
<if test="arg0 != null and arg0 != ''">
<if test="arg1 != null and arg1 != ''">
<!--dao接口方法:List<User> search3(String username, String sex); 没有参数集-->
<select id="search3" resultType="user">
select * from user
<where>
<if test="arg0 != null and arg0 != ''">
and username like #{arg0}
</if>
<if test="arg1 != null and arg1 != ''">
and sex = #{arg1}
</if>
</where>
</select>
2)为参数取别名,在参数的前面
用别名来称呼、起别名的方法:
在方法的形参列表中加入@ param注解 ,注解的括号内写别名
List<User> search4 (@Param("username")String username, @Param("sex") String sex);
<!--
List<User> search4(@Param("username")String username, @Param("sex") String sex);
; 为参数取了别名
-->
<select id="search4" resultType="user">
<!--select * from user-->
<include refid="selectUser"/>
<where>
<if test="username != null and username != ''">
and username like #{username}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
</where>
</select>
3.7 使用foreach
dao接口方法
package com.studymyself.dao;
import com.studymyself.entity.User;
import java.util.List;
//接口操作t_user表
public interface UserDao {
//通过if动态sql语句查询数据,参数必须是Java对象
public List<User> selectByIf(User user);
//通过if和where动态sql语句查询数据,参数必须是Java对象
public List<User> selectByWhere(User user);
//执行查询有in条件的sql语句
public List<User> selectByForeach(List<Integer> integers);
}
mapper文件如下,是在in后面添加foreach标签
<?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="com.studymyself.dao.UserDao">
<select id="selectByIf" resultType="User">
select id,loginName,loginPwd,realName from t_user
where
<!--上面表示主语句-->
<!--if表示这个标签中传递过来的Java对象的id字段值大于0
就将if标签中的sql语句块拼接到主语句末端中去-->
<if test="id > 0">
id=#{id}
</if>
<!--传过来的java对象中的realName值不为空或不是空字符串
就将if标签中的sql语句块拼接到主语句末端中去-->
<if test="realName!= null or realName !='' ">
or realName = #{realName}
</if>
</select>
<select id="selectByWhere" resultType="User">
select id,loginName,loginPwd,realName from t_user
<where>
<if test="id > 0">
or id=#{id}
</if>
<if test="realName!= null and realName !='' ">
or realName = #{realName}
</if>
</where>
</select>
<select id="selectByForeach" resultType="User">
select id,loginName,loginPwd,realName from t_user where id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
</mapper>
测试程序
@Test
public void testSelectByForeach(){
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//通过SqlSession对象中的方法获取实现类对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<Integer> list = new ArrayList<>();
//添加数据
list.add(1);
list.add(4);
list.add(7);
List<User> users = userDao.selectByForeach(list);
for (User user:
users) {
System.out.println(user);
}
}
日志
Opening JDBC Connection
Mon Feb 01 02:22:32 CST 2021 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Created connection 762476028.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2d7275fc]
==> Preparing: select id,loginName,loginPwd,realName from t_user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 4(Integer), 7(Integer)
<== Columns: id, loginName, loginPwd, realName
<== Row: 1, xxpiaozhiyan, yanyan, 朴智妍
<== Row: 4, lulujintaiyan, zxcvbnm, 金泰妍
<== Row: 7, 6666666@mail.com, 8888888, 中国大使馆
<== Total: 3
User{id=1, loginName='xxpiaozhiyan', loginPwd='yanyan', realName='朴智妍'}
User{id=4, loginName='lulujintaiyan', loginPwd='zxcvbnm', realName='金泰妍'}
User{id=7, loginName='6666666@mail.com', loginPwd='8888888', realName='中国大使馆'}
3.8 SQL语句代码片段
--sql代码片段就是复用映射文件中一些sql语句块,映射文件中的一些sql语句块需要重复使用时,可以使用该方法
使用步骤:
1、先使用<sql>标签定义复用的sql语句
<sql id="自定义的唯一名称">
sql语句
</sql>
2、在需要该sql语句的位置使用<include>标签表示该位置就是定义的sql语句
<include refid="前面定义的代码片段的id" />
--在mapper文件中具体使用如下:
<!--定义sql片段-->
<sql id="selectUserSql">
select id,loginName,loginPwd,realName from t_user
</sql>
<select id="getAll" resultType="User">
<!--使用sql片段-->
<include refid="selectUserSql"/>
</select>