CRUD标签都有一个属性parameterType,底层的statement通过它指定接收的参数类型。入参数据有以下几种类型:HashMap,基本数据类型(包装类),实体类;
设置传入这条语句的参数类的完全限定名或别名。
这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数类型。
说明:
在mybatis中入参的数据类型分为2大类:
基本数据类型:int,string,long,Date等;
复杂数据类型:类(pojo)和Map;
说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。
【示例】
public interface UserMapper {
//根据id查询
User findById(Integer id);
}
【基本类型数据】
<select id="findById" resultType="user" parameterType="int">
select * from user where id = #{id}
select>
【pojo类型】
<insert id="savetUser" parameterType="User">
INSERT INTO user(...) values(#{userName},...);
insert>
底层原理:
User类:user_name属性,自动生成setter或者getter方法时,getUserName
当我们向xml传入#{user_Name}---->getUser_Name--->利用反射根据方法名称获取方法对象--->报反射异常
综上:
1)传入的时pojo的话,传入xml的变量名称与pojo类下属性名称要一致;
2)pojo中属性名称定义要尽量遵循驼峰命名,或者在自动生成getter或者setter方法时,自己手动纠正;
说明:对于parameterType属性可以不书写,那么MyBatis 就会通过类型处理器(TypeHandler) 根据接口中的方法User queryById(Integer id)参数类型推断出具体传入语句的参数类型。
需求:新增一条数据成功后,将这条数据的主键封装到实体类中,并查看主键的值。
方式1:使用insert标签的**子标签selectKey+last_insert_id()**函数实现实现
属性 | 说明 |
---|---|
keyColumn | 主键在表中对应的列名 |
keyProperty | 主键在实体类中对应的属性名 |
resultType | 主键的数据类型 |
order | BEFORE:会首先选择主键,设置 keyProperty 然后执行插入语句 AFTER: 在添加语句后执行查询主键的语句 |
测试代码:
1)接口
/**
* 添加用户
* 返回值时影响的行数
* @param user
* @return
*/
Integer addUserAndGetFkId(User user);
2)映射文件:
<insert id="addUserAndGetFkId">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
selectKey>
insert>
3)测试
@Test
public void test13(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setUsername("唐僧2");
user.setSex("男");
Date date = Date.valueOf("2020-12-15");
user.setBirthday(date);
user.setAddress("长安");
Integer count = userMapper.addUserAndGetFkId(user);
System.out.println(user.getId());
//mybatis默认事务手动提交
MybatisUtil.commit();
MybatisUtil.close();
}
4)效果
方式2:使用insert标签的属性useGeneratedKeys,keyProperty,keyColumn实现
参数说明:
属性 | 说明 |
---|---|
useGeneratedKeys | true 获取自动生成的主键,相当于select last_insert_id() |
keyColumn | 表中主键的列名 |
keyProperty | 实体类中主键的属性名 |
说明:直接在insert标签中增加属性的方式,只适合于支持自动增长主键类型的数据库,比如MySQL或SQL Server;
测试代码:
接口:
/**
* 添加用户
* 返回值时影响的行数
* @param user
* @return
*/
Integer addUserAndGetFkId2(User user);
映射文件:
<insert id="addUserAndGetFkId2" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
insert>
测试:
@Test
public void test14(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setUsername("唐僧3");
user.setSex("男");
Date date = Date.valueOf("2020-12-15");
user.setBirthday(date);
user.setAddress("长安");
Integer count = userMapper.addUserAndGetFkId2(user);
System.out.println(user.getId());
//mybatis默认事务手动提交
MybatisUtil.commit();
MybatisUtil.close();
}
效果:
说明:使用方式2的话,数据库必须支持主键自增;
单个参数:接口方法传入一个参数
User queryById(Integer id);
1、通过#{参数名}接收
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{id}
select>
2、通过#{任意变量名}接收
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{abc}
select>
如果接口传入的时单个参数,可以在xml中使用任意变量取接收,但是不建议乱写,最好见名知意;
需求:根据用户名和性别查询用户
2.1 接口传参
/**
* 需求:根据用户名和性别查询用户
* @param name
* @param sex
* @return
*/
List<User> findUsersByUserNameAndSex(String name,String sex);
2.2 UserMapper.xml
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{name} and sex=#{sex}
select>
2.3 测试类
@Test
public void test15(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
List<User> users = mapper.findUsersByUserNameAndSex("孙悟空", "男");
System.out.println(users);
MybatisUtil.close();
}
2.3 结果
此时会报参数绑定异常
解决方案:
方式1、使用参数索引获取:arg0,arg1(了解,不推荐)
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{arg0} and sex=#{arg1}
select>
说明: 接口参数顺序要与arg0与arg1顺序一致;
方式2:使用参数位置获取:param1,param2(了解,不推荐)
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{param1} and sex=#{param2}
select>
方式3:使用命名参数获取,明确指定传入参数的名称:(掌握)
步骤一:在接口中传入参数时通过@Param指定参数名称
/**
* 需求:根据用户名和性别查询用户
* @param name
* @param sex
* @return
*/
List<User> findUsersByUserNameAndSex(@Param("name") String name,@Param("sex") String sex);
步骤二:在接收参数时,通过指定的名称获取参数值;
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{name} and sex=#{sex}
select>
小结:
1)入参是单个参数(单个参数是单值类型)
在xml中可以任意变量取接收,但是建议名称见名知意;
2)多个参数
建议使用方式3通过注解方式给参数取别名,然后再xml中使用这个别名
eg:
List findUsersByUserNameAndSex(@Param("name") String name,@Param("sex") String sex);
那么在xml中:
说明:接口方法传入pojo类型的数据时,mybatis底层直接使用pojo封装数据。 sql语句中 #{username}取值==》到pojo中调用 getUsername(){}
测试代码:
接口:
/**
* 插入功能
* @param user
*/
void saveAndGetkey2(User user);
映射文件:
<insert id="saveAndGetkey2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into user values(null,#{username},#{age},#{birthday},#{sex},#{address})
insert>
测试:
@Test
public void testAdd3(){
User user = new User();
user.setAddress("北京");
user.setUsername("于彪");
user.setBirthday(new Date());
mapper.saveAndGetkey2(user);
//直接获取返回的主键id值
System.out.println(user.getId());
}
小结:
使用pojo的话,xml中参数一定名称要与pojo中的属性名称一致,否则报错(binding exception);
需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
注意事项:参数map中的key值是与SQL语句中 #{} 的取值名称一致。
代码实现:
2.1 接口:
/**
* 需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
* @return
*/
User loginByNameAndSex(Map map);
2.2 映射文件:
<select id="loginByNameAndSex" resultType="user" parameterType="map">
select * from user where user_name=#{name} and sex=#{sex}
select>
2.3 测试:
@Test
public void test16(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
HashMap<String, String> map = new HashMap<>();
//map中的key要与xml映射文件下的参数名称要一致
map.put("name","孙悟空");
map.put("sex","男");
User user = mapper.loginByNameAndSex(map);
System.out.println(user);
MybatisUtil.close();
}
小结:
1)入参类型pojo注意事项?
xml映射文件下的参数名称要与pojo类中属性名称一致;
2)入参是map注意事项?
xml映射文件下的参数名称要与map中key的名称一致;
参数值的获取指的是statement获取接口方法中传入的参数。
获取参数,有两种方式:#{}和${};
以根据id查询为例测试#{}和${}区别:
使用#{}接收参数:
select * from user where id=? //预编译处理,防止sql注入
使用${}接收参数:
select * from user where id=1 //参数值直接拼接到sql中,会有sql注入的风险
使用#{}的sql是进行预编译的,可以防止sql注入;
${}
取值注意:${id} 获取id值时,必须使用命名参数取值@param:
补充:如果是取单个值,也可使用${value}获取
1)映射文件
<select id="findById2" resultType="user" parameterType="int">
select * from user where id=${id}
select>
2)接口
/**
* 根据id查询用户信息 测试${}
* @param id
* @return
*/
User findById2(@Param("id") Integer id);
3)测试
@Test
public void test12(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
User user = mapper.findById2(1);
System.out.println(user);
MybatisUtils.close();
}
小结:
面试:#{}和${}取值有什么区别?
#{}
sql进行预编译处理,防止sql注入;
${}
参数与sql直接拼接,有sql注入的风险;
在一些特殊的应用场景中,需要对SQL语句部分(不是参数)进行拼接,这个时候就必须使用${}来进行拼接,不能使用#{}.例如:
1、企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,如:2017_user,2018_user....,对这些表进行查询就需要动态把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其预编译,所以只能使用${}进行拼接:
SELECT * FROM ${year}_user;
2、根据表名查询数据总记录数:
SELECT COUNT(*) FROM user
SELECT COUNT(*) FROM order
SELECT COUNT(*) FROM ${tableName}
简言之:如果需要设置到SQL中的不是查询的条件,只能使用${}拼接;
示例:
需求:根据输入的表名统计指定表下的总记录数;
1)接口
/**
* 需求:根据输入的表名统计指定表下的总记录数;
*/
Integer countByTableName(@Param("tableName") String tableName);
2)映射文件
<select id="countByTableName" resultType="integer">
select count(*) from ${tableName}
select>
3)测试类
@Test
public void test18(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
Integer count = mapper.countByTableName("user");
System.out.println(count);
MybatisUtil.close();
}
小结:
${}使用方式及场景?
使用方式:
1.接口中@param给参数取别名 2.在xml中使用注解的别名${注解别名}
场景:
一切非条件查询的参数拼接可以使用${}
【 ${}获取单个值】
${}
获取单个值时,最好是通过命名参数的形式获取。如果不指定参数的,也可以使用${value}来获取传入的单个值;
传入参数:没有指定参数名称
User selectUserById(Integer id);
获取参数通过${value}获取
<select id="selectUserById" resultType="user">
select * from user where id = ${value}
select>
【${}获取配置文件中的值】
有时候,我们如果非要使用$来接收参数,将login修改如下:
<select id="queryByUserNameAndSex" resultType="User">
SELECT * FROM user WHERE user_name = '${jdbc.user}' AND sex = #{sex}
select>
说明:上述sql语句中:SELECT * FROM user WHERE user_name = ‘${username}’ AND sex = #{sex}
对于 ' u s e r n a m e ′ 加单引号是因为 {username}' 加单引号是因为 username′加单引号是因为{}获取数据的方式直接将获取的数据拼接到字符串上,并不会加引号,如果获取的值是数值型,没有问题,但是如果是字符类型就会有问题,所以需要加上引号进行拼接。
使用${}注意事项:
1.使用${变量}方式获取变量值时,不要与全局的properties下定义的参数名称冲突,否则数据注入错误;
2.使用${变量}传入字符串类型时,需要自己维护字符串的上引号;
在使用原生的JDBC操作时,对于结果集ResultSet,需要手动处理。
mybatis框架提供了resultType和resultMap来对结果集进行封装。
注意:只要一个方法有返回值需要处理,那么 resultType和resultMap必须有一个
从sql语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
例如 int ,string ===>resultType=“书写对应的基本类型别名或者全名即可”
测试:findNameById:
1)基本类型 int short double ... 别名: _基本类型名称
2)包装类 类 String ArrayList .... 别名:类名首字母小写
3)自定义类 扫包取别名 类首字母小写(大写也可)
测试:findById:
当返回值为List集合时,resultType需要设置成集合中存储的具体的pojo数据类型:
测试:findAllUsers:
需求:查询id是1的数据,将查询的结果封装到Map
1)定义接口
/**
* 需求:查询id是1的数据,将查询的结果封装到Map中
* @param id
* @return
*/
Map<String,Object> findMapById(@Param("id") Integer id);
2)定义映射文件配置
<select id="findMapById" resultType="map">
select id,user_name as userName,address from user where id=#{id}
select>
3)测试:
@Test
public void test19(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
Map<String, Object> map = mapper.findMapById(1);
System.out.println(map);
MybatisUtil.close();
}
4)效果
需求:查询数据表所有的数据封装到Map
要求: Key值为一条记录的主键,Value值为pojo的对象.
说明:需要在接口的方法上使用注解@MapKey指定数据表中哪一列作为Map集合的key,否则mybatis不知道具体哪个列作为Map集合的key.
1)映射文件:
<select id="findAllToMap" resultType="map">
select id,user_name as name,birthday,sex,address from user
select>
2)接口:
/**
* 获取所有用户,其中key为id值,value为user对象
* @return
*/
@MapKey("id")
Map<Integer,User> findAllToMap();
3)测试:
@Test
public void test16(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
Map map = mapper.findAllToMap();
System.out.println(map);
MybatisUtils.close();
}
小结:
1)map接收单条记录
map中的key就是查询的表的字段名称,如果使用as区别名,那么可以就是对应的别名的名称;
2)map接收多条记录
1)需要指定作为key的字段,一般是主键字段 @MapKey("指定字段名称")
2)指定每一条记录封装的对象类型;
1.正常开发中,数据库字段名称与Pojo类属性名称不一致时,一般通过驼峰映射或者As关键字取别名可以搞定,但是很多场景下,对于复杂的orm映射,上述的2种方式就不能适用了;
2.ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决复杂映射问题:
1. POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式,比如id和userId)
2. 完成高级查询,比如说,一对一、一对多、多对多。
使用resultMap完成结果集的封装(resultSet===》JavaBean)
手动配置实体类属性和表字段映射关系的步骤如下:
1、 配置自定义结果集<resultMap>
2、 配置id映射
3、 配置其他普通属性的映射
**步骤一:**将驼峰匹配注释掉
一旦注释掉驼峰匹配,那么再通过findAll查询的结果中,用户名就无法封装了,此时我们可以尝试使用ResultMap来解决这个问题。
<settings>
<setting name="mapUnderscoreToCamelCase" value="false"/>
settings>
**步骤二:**配置resultMap
resultMap标签的作用:自定义结果集,自行设置结果集的封装方式
id属性:resultMap标签的唯一标识,不能重复,一般是用来被引用的
type属性:结果集的封装类型
autoMapping属性:操作单表时,不配置默认为true,如果pojo对象中的属性名称和表中字段名称相同,则自动映射。
在映射文件中自定义结果集类型:
<resultMap id="userResultMap" type="user" autoMapping="true">
<id column="id" property="id">id>
<result column="user_name" property="name">result>
resultMap>
步骤三:修改查询语句的statement
在查询语句的select标签中通过resultMap属性可以引用自定义结果集作为数据的封装方式
<resultMap id="userMap" type="user" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="username"/>
resultMap>
<select id="findByIdUseResutMap" resultMap="userMap">
select * from user where id=#{id}
select>
定义接口:
/**
* 使用resultMap标签自定义映射规则
* @param id
* @return
*/
User findByIdUseResutMap(@Param("id") Integer id);
测试代码:
@Test
public void test21(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = mapper.findByIdUseResutMap(1);
System.out.println(user);
MybatisUtil.close();
}
**注意:**测试完记得将驼峰命名的配置重新开启,因为其他的测试方法还要用。
1.resultMap标签的作用?
1)提高了代码的复用性;
2)结果集映射的;(将查询的结果映射到pojo类下)
2.resultMap有哪些属性和子标签?
<resultMap id="唯一标识" type="映射的pojo类" autoMapping="true">
<id column="表中主键字段" property="pojo类中主键对应的属性名称"/>
<result column="表中非主键字段" property="pojo类中非主键对应的属性名称"/>
resultMap>
sql标签可以定义一个sql片段,在需要使用该sql片段的地方,通过
<sql id="userCommonSql">
id,user_name,birthday,sex,address
sql>
注意:SQL片段必须设置id属性;
在SQL语句中通过
标签引入SQL片段;
<select id="findByIdUseResutMap" resultMap="userMap">
select <include refid="userCommonSql"/> from user where id=#{id}
select>
很多时候同一个sql片段,可能在多个映射文件中都有使用,如果每一个映射文件都编写一个相同的sql就比较麻烦,因此可以将通用的sql片段都定义在一个专门存放sql片段的映射文件中,然后由其他映射文件引用它即可。
如下,在src目录下新增CommonSQL.xml文件:
复制一份映射文件,将SQL片段写入即可
【CommonSQL.xml】
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="CommonSql">
<sql id="userSql">
id,user_name,birthday,sex,address
sql>
<sql id="empSql">
eid,ename,age
sql>
mapper>
定义好sql片段的映射文件之后,接下来就该使用它了,首先应该把该映射文件引入到mybatis的全局配置文件中(mybatis-config.xml):
<mappers>
<mapper resource="CommonSql.xml"/>
<package name="com.heima.mapper"/>
mappers>
最后在需要使用该sql片段的地方通过include标签的refId属性引用该sql片段:
在UserMapper.xml的映射文件中,进一步改造根据用户名查询用户信息
<select id="findUserById2" resultMap="userMap" parameterType="int">
select <include refid="CommonSql.userSql"/> from user where id = ${id}
select>
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
场景:
【需求】:查询男性用户,如果输入了用户名,按用户名模糊查询,如果没有输入用户名,就查询所有男性用户。
在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 开始精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的 OGNL 的表达式来淘汰其它大部分元素。
了解:
OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。
动态SQL中的业务逻辑判断需要使用到以下运算符: ognl表达式
1. e1 or e2 满足一个即可
2. e1 and e2 都得满足
3. e1 == e2,e1 eq e2 判断是否相等
4. e1 != e2,e1 neq e2 不相等
5. e1 lt e2:小于 lt表示less than
6. e1 lte e2:小于等于,其他gt(大于),gte(大于等于) gt 表示greater than
7. e1 in e2
8. e1 not in e2
9. e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
10. !e,not e:非,求反
11. e.method(args)调用对象方法
12. e.property对象属性值 user.userName
13. e1[ e2 ]按索引取值,List,数组和Map
14. @class@method(args)调用类的静态方法
15. @class@field调用类的静态字段值
常见标签如下:
if:判断 if(1 gt 2){}
choose (when, otherwise):分支判断 switch:多选一
where标签
set标签
foreach:循环遍历标签
语法格式:
<if test="判断条件">
满足条件sql加入拼接
if>
说明:
1)if标签:判断语句,用于进行逻辑判断的。如果判断条件为true,则执行if标签的文本内容
2)test属性:用来编写表达式,支持ognl;
【需求】:查询男性用户,如果输入了用户名,按用户名模糊查询,如果没有输入用户名,就查询所有男性用
要求:
1.使用
2.test属性:使用OGNL表达式,完成具体的判断业务逻辑;
1)定义接口
/**
* 【需求】:查询男性用户,如果输入了用户名,
* 按用户名模糊查询,如果没有输入用户名,就查询所有男性用
*/
List<User> findUsersByUserName(@Param("userName") String userName);
2)定义接口方法对应的映射文件信息
<select id="findUsersByUserName" resultMap="userMap">
select * from user where sex='男'
<if test="userName!=null">
and user_name like concat('%',#{userName},'%')
if>
select>
3)测试方法
@Test
public void test22(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
//select * from user where sex='男'
//List users = mapper.findUsersByUserName(null);
//select * from user where sex='男' and concat('%',?,'%');
List<User> users = mapper.findUsersByUserName("唐僧");
System.out.println(users);
MybatisUtil.close();
}
4)效果:
小结:
if标签使用方式?
<if test="条件表达式">
sql代码
if>
choose标签:分支选择(多选一,遇到成立的条件即停止)
when子标签:编写条件,不管有多少个when条件,一旦其中一个条件成立,后面的when条件都不执行。
test属性:编写ognl表达式
otherwise子标签:当所有条件都不满足时,才会执行该条件。
语句示例:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
when>
<otherwise>
AND featured = 1
otherwise>
choose>
select>
需求:
编写一个查询方法,设置两个参数,一个是用户名,一个是住址。
根据用户名或者住址查询所有男性用户:
如果输入了用户名则按照用户名模糊查找,
否则就按照住址查找,两个条件只能成立一个,
如果都不输入就查找用户名为“孙悟空”的用户。username address
【需求分析】
-- 输入了用户名
select * from user where sex='男' and user_name like '%参数%'
-- 没有输入姓名,但是输入了地址
select * from user where sex='男' and address = '参数'
-- 什么也没输入
select * from user where sex='男' and user_name = '孙悟空'
1)定义接口方法
List<User> findUsersByNameAndAddress(@Param("name") String userName,@Param("address") String address);
2)定义接口方法对应的映射文件信息
<select id="findUsersByNameAndAddress" resultMap="userMap">
select * from user where sex='男'
<choose>
<when test="name!=null">
and user_name like concat('%',#{name},'%')
when>
<when test="address!=null">
and address=#{address}
when>
<otherwise>
and user_name='孙悟空'
otherwise>
choose>
select>
3)测试
@Test
public void test23(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
//select * from user where sex='男' and user_name like concat('%',?,'%')
//List users = mapper.findUsersByNameAndAddress("唐僧", "花果山水帘洞");
//select * from user where sex='男' and address=?
//List users = mapper.findUsersByNameAndAddress(null, "花果山水帘洞");
//select * from user where sex='男' and user_name='孙悟空'
List<User> users = mapper.findUsersByNameAndAddress(null, null);
System.out.println(users);
MybatisUtil.close();
}
小结:
多选一使用方式?
规则:多选一,自上而下执行,遇到满足条件就退出,否则otherwise下的语句参与拼接
<choose>
<when test="条件">
sql代码
when>
<when test="条件">
sql代码
when>
....
<otherwise>
sql代码
otherwise>
choose>
where标签:拼接多条件查询时 1、能够添加where关键字; 2、能够去除多余的and或者or关键字
需求:按照如下条件查询所有用户
如果只输入了用户名按照用户名进行查询;
select * from user where user_name like concat('%',#{userName},'%');
如果只输入住址,按住址进行查询;
select * from user where address=#{address};
如果两者都输入,则按照两个条件查询;
select * from user where user_name like concat('%',#{userName},'%') and address=#{address};
如果两者都不合符条件,全表查询;
select * from user
where多条件语法格式:
1)定义接口
List<User> findByNameAndAddress(@Param("name") String name,@Param("address") String address);
2)定义xml
<select id="findByNameAndAddress" resultMap="userMap">
select * from user
<where>
<if test="name!=null">
user_name like concat('%',#{name},'%')
if>
<if test="address!=null">
and address=#{address}
if>
where>
select>
3)测试
@Test
public void test24(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
//select * from user where user_name like concat('%',?,'%')
//List users = mapper.findByNameAndAddress("唐僧", null);
//select * from user where address=?
//List users = mapper.findByNameAndAddress(null, "长安");
//select * from user
//List users = mapper.findByNameAndAddress(null, null);
//select * from user user_name like concat('%',?,'%') and address=?
List<User> users = mapper.findByNameAndAddress("唐僧", "花果山水帘洞");
System.out.println(users);
MybatisUtil.close();
}
小结:
通过使用
作用:
1)适当添加where关键字
2)去除被where标签包裹的多余的and或者or关键字
语法格式:
and sql1
and sql2
set标签:在update语句中,可以自动添加一个set关键字,并且会将动态sql最后多余的逗号去除。
语法格式:
<update .......>
update 表名
<set>
<if test='条件'>
字段名1=值1,
if>
<if test='条件2'>
字段名2=值2,
if>
....
set>
where 条件;
update>
案例:修改用户信息,如果参数user中的某个属性为null,则不修改。
1)定义接口方法
/**
* 案例:修改用户信息,如果参数user中的某个属性为null,则不修改。
* @param user
*/
void updateSelectiveUser(User user);
2)定义映射文件
<update id="updateSelectiveUser">
update user
<set>
<if test="username!=null">
user_name=#{username},
if>
<if test="birthday!=null">
birthday=#{birthday},
if>
<if test="sex!=null">
sex=#{sex},
if>
<if test="address!=null">
address=#{address}
if>
set>
where id=#{id}
update>
3)测试
@Test
public void test25(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setUsername("沙悟净");
user.setId(2);
user.setSex("男");
//update user set user_name=?,sex=? where id=?
mapper.updateSelectiveUser(user);
MybatisUtil.commit();
MybatisUtil.close();
}
小结:
set标签的作用?
1)去除被set标签包裹的多余的逗号','
2)添加set关键字;
foreach标签:遍历集合或者数组
<foreach collection="集合名或者数组名" item="元素" separator="标签分隔符" open="以什么开始" close="以什么结束">
#{元素}
foreach>
collection属性:接收的集合或者数组,集合名或者数组名
item属性:集合或者数组参数中的每一个元素
separator属性:标签分隔符
open属性:以什么开始
close属性:以什么结束
举例:
java接口方法:
List<Post> selectPostIn(@Param("ids") List<Integer> ids);
sql映射文件配置:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT * FROM POST P
WHERE ID in
<foreach collection="ids" item="item" open="(" separator="," close=")">
#{item}
foreach>
select>
需求:按照id值是1,2,3来查询(删除)用户数据;
1)定义接口
List<User> findByIds(@Param("ids") List<Integer> ids);
2)定义xml映射
<select id="findByIds" resultMap="userMap">
select * from user where id in
<foreach collection="ids" item="item" separator="," open="(" close=")">
#{item}
foreach>
select>
3)测试
@Test
public void test21(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
//List integers = Arrays.asList(1, 3, 5, 7, 9);
//select * from user where id in(?,?,?,?,?)
List<User> users = mapper.findByIds(Arrays.asList(1, 3, 5, 7));
System.out.println(users);
MybatisUtils.close();
}
小结:
foreach标签属性作用?
作用:
遍历从接口中出入的集合的;
语法格式:
#{集合中每一个元素}
mapper.xml中的动态SQL:
<delete id="deleteByIds" >
delete from user where id in
<foreach collection="ids" item="uid" separator="," open="(" close=")">
#{uid}
foreach>
delete>
接口方法:
/**
* 根据id批量删除
* @param ids
* @return
*/
Integer deleteByIds(@Param("ids") List<Integer> ids);
单元测试方法:
/**
* 测试 批量删除
*/
@Test
public void deleteByIds(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
Integer counts = mapper.deleteByIds(Arrays.asList(7, 8));
System.out.println(counts);
session.close();
}
mapper.xml中的动态SQL:
<insert id="insertUsers">
insert into user values
<foreach collection="users" item="user" separator=",">
(null,#{user.username},#{user.birthday},#{user.sex},#{user.address})
foreach>
insert>
接口方法:
/**
* 批量插入用户
* @param users
* @return
*/
Integer insertUsers(@Param("users") List<User> users);
单元测试方法:
/**
* 批量插入用户
*/
@Test
public void insertUsers(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setBirthday(Date.valueOf("2023-08-20"));
user.setAddress("上海");
user.setSex("男");
user.setUsername("高圆圆");
User user2 = new User();
user2.setBirthday(Date.valueOf("2023-08-20"));
user2.setAddress("上海");
user2.setSex("男");
user2.setUsername("刘亦菲");
ArrayList<User> list = new ArrayList<>();
list.add(user);
list.add(user2);
Integer count = mapper.insertUsers(list);
System.out.println(count);
}
核心的动态sql标签有哪些,各有什么作用?
1)if标签
sql语句(如果满足条件,当前sql片段参与拼接)
2)choose when otherwise多选一 自上而下执行,遇到满足条件则退出,否则otherwise小的sql参与拼接
sql语句(如果满足条件,就参与sql拼接)
sql语句(如果满足条件,就参与sql拼接)
sql语句
3)where
作用:1)适当添加where关键字 2)去除多余and或者or关键字
4)set
作用:1)添加set关键字 2)去除多余的逗号
5)foreach标签:
作用:
遍历从接口中出入的集合的;
语法格式:
#{集合中每一个元素}
我们在编写Mapper映射文件时,有时候需要使用到一些诸如:>
,<
之类的特殊字符。这些字符不能直接书写在xml文件中,需要我们对其处理。
处理方式:使用转义字符代替特殊字符。
转义字符 | sql符号 | 说明 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | 和号 |
' | ’ | 单引号 |
" | " | 双引号 |
举例:批量将id小于3的用户性别改为男,在映射文件中直接写<号,xml约束会报错!
<select id="findUsersLt" resultMap="userMap">
select * from user where id < #{id}
select>
表与表的关系:
一对一: ab两表的关系,由任意一张表维护(外键) 比如:b表维护ab的管理,那么在b表中创建一个字段aid,但是这个字段不添加外键约束;
一对多:ab两张表 比如:从a看是一个a对应b的多条数据,但是从b看是一个b只能对应一个a的数据;
多对多:ab两张表 比如:从a看是一个a对应b的多条数据,同时从b看是一个b对应a表的多条数据;
说明:通过用户,订单,商品,已经订单商品明细表练习多表关联查询操作;
创建java项目,导入jar包和log4j日志配置文件以及连接数据库的配置文件;
运行资料中的sql脚本:mybatis.sql
注:可以根据id查询用户信息为例搭建起工程;
1.定义接口
public interface UserMapper {
/**
* 根据id查询用户信息
* @param id
* @return
*/
User findByUserId(@Param("id") Long id);
}
2.配置映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.mapper.UserMapper">
<select id="findByUserId" resultType="user">
select * from tb_user where id=#{id}
select>
mapper>
3.单元测试
public class TestAll {
@Test
public void test1(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = mapper.findByUserId(1l);
System.out.println(user);
MybatisUtil.close();
}
}
工程结构:
一对一映射语法格式:
<resultMap id="映射ID" type="主表实体名称" autoMapping="true" >
......
<association property="主表实体中对应从表的属性名称" javaType="从表实体类型" autoMapping="true">
association>
resultMap>
需求:通过订单编号20140921003查询出订单信息,并查询出下单人信息。
说明:一个订单只能对应一个用户信息;
3.1 需求分析
两种实现方式:
-- 方式1:分步查询
-- 1.1根据订单编号查询订单信息
select * from tb_order where order_number='20140921003';-- user_id:1
-- 1.2根据user_id=1查询下单人信息
select * from tb_user where id=1;
-- 方式2:关联查询
select tb_order.id as order_id,tb_order.order_number,tb_user.*
from tb_order,tb_user
where
tb_user.id=tb_order.user_id
and tb_order.order_number='20140921003';
3.2 订单实体添加属性映射
public class Order {
private Integer id;
private String orderNumber;
private User orderUser;
//getter setter toString
}
3.3 添加order接口及方法
public interface OrderMapper {
/**
* 根据订单编号查询订单信息,包含下单人信息
* @param orderNumber
* @return
*/
Order findOrderByOrderNumber(@Param("orderNumber") String orderNumber);
}
3.4 创建order映射文件,编写SQL
说明:配置关联对象一对一映射关系,语法格式:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.mapper.OrderMapper">
<resultMap id="orderMap" type="Order" autoMapping="true">
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
<association property="orderUser" javaType="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
association>
resultMap>
<select id="findOrderByOrderNumber" resultMap="orderMap">
select
tb_order.id as order_id,
tb_order.order_number,
tb_user.*
from tb_order,
tb_user
where tb_user.id = tb_order.user_id
and tb_order.order_number = #{orderNumber}
select>
mapper>
3.5 测试
@Test
public void test2(){
OrderMapper mapper = MybatisUtil.getMapper(OrderMapper.class);
Order order = mapper.findOrderByOrderNumber("20140921003");
System.out.println(order);
MybatisUtil.close();
}
3.6 效果
一对一查询总结:
1.映射规则配置?
PPT演示:
2.一对一关联查询实现步骤?
1)维护pojo对象之间一对一的关系:
eg:Order 包含属性:User类型的变量
2)配置一对一的映射关系
核心映射标签预览:
<resultMap id="xx" type="xx" autoMapping="true">
<id column="xx" property="xx"/>
<collection property="xxx" javaType="list" ofType="xxx" autoMapping="true">
<id column="xx" property="xx"/>
<result column="xx" property="xx"/>
collection>
resultMap>
需求:查询id为1的用户及其订单信息
【分析】
一个用户可以有多个订单。
一个订单只能属于一个用户。
用户(1)-----订单(n)
-- 方式1:分步查询
-- 1.1 根据id查询用户信息
select * from tb_user where id=1; -- id=1
-- 1.2 根据用户id查询订单集合
select * from tb_order where user_id=1;
-- 方式1:一步查询
select
tb_user.*,
tb_order.id as order_id,
tb_order.order_number
from tb_user,
tb_order
where tb_user.id = tb_order.user_id
and tb_user.id = 1;
【步骤】
第一步:查询SQL分析;
第二步:添加关联关系;
第三步:编写接口方法;
第四步:编写映射文件;
第五步:测试
【实现】
1.User实体添加映射关系
public class User implements Serializable{
private List<Order> orders;
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
//0-女 1-男
private Integer sex;
// getter and setter and toString
}
2.编写接口
/**
* 根据用户id查询用户信息,包含订单集合信息
* @param id
* @return
*/
User findUserAndOrdersByUserId(@Param("id") Long id);
3.编写sql映射文件关联订单集合
<resultMap id="userMap" type="user" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<collection property="orders" javaType="list" ofType="order" autoMapping="true">
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
collection>
resultMap>
<select id="findUserAndOrdersByUserId" resultMap="userMap">
select
tb_user.*,
tb_order.id as order_id,
tb_order.order_number
from tb_user,
tb_order
where tb_user.id = tb_order.user_id
and tb_user.id = #{id}
select>
4.测试:
@Test
public void test3(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = mapper.findUserAndOrdersByUserId(1l);
System.out.println(user);
MybatisUtil.close();
}
5.效果
一对多总结:
1.映射规则配置?
PPT演示:
小结:
1.在一对多的场景中,一般主表中通过创建一个集合属性来包含从表的数据
如:user类包含List\ orders);
2.如何使用collection标签建立对集合的关联映射?
<resultMap id="唯一标识" type="映射的类" autoMapping="true">
<id column="主键字段" property="映射类中属性名称"/>
<result column="非主键字段" property="映射类中属性名称"/>
<collection property="映射的类中属性名称" javaType="list" ofType="集合泛型" autoMapping="true">
<id column="主键字段" property="java类型中属性名称"/>
<result column="非主键字段" property="java类型中属性名称"/>
collection>
resultMap>
多对多查询本质是一对多和一对一查询的组合,核心标签用法如下:
<resultMap id="xx" type="xx" autoMapping="true">
<id column="xx" property="xx"/>
<collection property="xx" javaType="list" ofType="xx" autoMapping="true" >
<id column="xx" property="xx"/>
<association property="xx" javaType="xx" autoMapping="true">
<id column="xx" property="xx"/>
association>
collection>
resultMap>
【需求】:查询订单号为20140921001的订单的详情信息即查询订单信息+订单中的商品信息;
【步骤】
第一步:需求分析;
第二步:添加关联关系;
第三步:编写SQL;
第四步:配置关联关系;
第五步:运行;
1、查询订单详情信息即:查询订单信息+订单中的商品信息;
2、订单信息在tb_order中,订单中的商品信息在tb_item中,这两个表是通过中间表 tb_orderdetail进行关联的。
3、关联查询思路:先查询订单表,通过订单表中的id关联中间表order_id,然后查询中间表,根据中间表的item_id关联商品表的id,最后查询商品表;
1)一个订单表中关联了多个订单详情信息,所以在订单表中添加List
属性;
2)一条订单详情记录中都包含了一条商品信息,所以需要在Orderdetail中添加一个Item属性;
在OrderMapper接口中新增,根据orderNumber查询订单及订单详情的方法;
说明:一定要记住这里给order表的id起别名是order_id,订单详情表的id起别名是detail_id,商品表item的id起别名是item_Id。在resultMap标签的id子标签中的column属性值书写对应的order_id、detail_id和item_Id.
【需求】根据订单号(20140921001)
查询订单信息
查询订单所属用户信息
查询订单中的详细商品信息
1)接口方法
2)映射文件
3)测试
如果两个ResultMap结果集有重叠的部分,可以通过extend属性继承简化;
继承后简化映射文件配置:
<resultMap id="orderMap4" type="order" extends="orderMap2" autoMapping="true">
<association property="user" javaType="user" autoMapping="true">
<id column="user_id" property="id"/>
<result column="user_name" property="userName"/>
association>
resultMap>
前提:查询的字段必须唯一!
一对一:
一对多:
多对多:
重点:
1)一对一映射配置
2)一对多映射配置
3)动态sql标签
4)#{}和${}区别(面试)
应用场景
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
延迟加载的好处
先从单表查询、需要时再从关联表去关联查询,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。
延迟加载的条件:
1)resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
2)延迟加载是针对分步查询而言的
Mybatis的延迟加载功能默认是关闭的
需要在mybatis-config.xml全局配置文件中通过setting标签配置来开启延迟加载功能
需要在mybatis-config.xml全局配置文件中通过setting标签配置来开启延迟加载功能开启延迟加载的属性:
lazyLoadingEnabled:全局性设置懒加载。默认为false,true表示开启延迟加载
aggressiveLazyLoading:false表示关闭积极加载
说明:这两个属性必须一起设置
【示例】
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
延迟加载需求:通过订单编号20140921003查询order并延迟加载user。就是演示上述演示过的一对一的表关系案例。
如果改成延迟加载,也就意味着,先查询order,等需要的时候再去查询user,那就相当于将上面的一条语句变成了两条语句:
1、通过订单编号查询order
2、通过查询出来的order中的user_id查询user
SQL语句:
分步查询:
#第一步:根据order_number查询订单信息;
SELECT * FROM tb_order WHERE order_number = '20140921003';
#第二步:根据订单信息中的user_id查询出下单人的信息;
SELECT * FROM tb_user WHERE id = 1;
1.在OrderMapper接口中新建:queryOrderUserLazy方法;
Order queryOrderUserLazy(String orderNumber);
2.编写映射文件,分部查询,通过association标签下的column和select属性获取参数并传递到子查询中;
<resultMap id="orderUserLazyMap" type="order" autoMapping="true">
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
<association property="user" javaType="user" column="user_id" select="com.heima.dao.UserMapper.queryById">
association>
resultMap>
<select id="queryOrderUserLazy" resultMap="orderUserLazyMap">
select * from tb_order where order_number=#{orderNumber}
select>
在mybatis-config.xml全局配置文件中,开启懒加载;
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
@Test
public void queryOrderUserLazy(){
Order order = orderMapper.queryOrderUserLazy("20140921001");
System.out.println("此时没有进行用户信息查询");
System.out.println(order.getOrderNumber());
//当我们使用user对象时,才从数据库中加载
User user = order.getUser();
System.out.println(user.toString());
}
小结:
延迟加载的利与弊
好处:先从单表进行查询数据,需要时再从关联表去关联查询,将会提高数据库性能,因为查询单表要比关联查询多张表速度快。
坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。