【Java】Mybatis学习笔记
目录
一.搭建Mybatis
二.Mybatis核心配置文件解析
1.environment标签
2.typeAliases
3.mappers
三.Mybatis获取参数值
四.Mybatis查询功能
五.特殊的SQL执行
1.模糊查询
2.批量删除
3.动态设置表名
4.添加功能获取自增的主键
六.自定义映射ResultMap
1.配置文件处理字段名和属性名的映射关系
2.使用ResultMap自定义映射处理映射关系
1)处理一对一的映射关系
2)处理多对一的映射关系
3)处理一对多的映射关系
七.动态SQL
1.if标签
2.where标签
3.trim标签
4.choose,when,otherwise标签
5.foreach标签
6.sql标签
八.Mybatis缓存
1.一级缓存
2.Mybatis二级缓存
Mybatis下载地址:GitHub - mybatis/mybatis-3: MyBatis SQL mapper framework for Java
Mybatis的优点:SQL语句加载在Java代码中耦合度高,导致硬编码内伤,不宜维护,Mybatis将SQL和Java编码分开,功能边界清晰;
一.搭建Mybatis
- 创建maven工程
- 创建mybatis核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--配置连接数据库的环境--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--引入mybatis的映射文件--><mappers><mapper resource="org/mybatis/example/BlogMapper.xml"/></mappers>
</configuration>
mysql不同版本注意事项:
1.驱动类driver-class-name
MYSQL5启动类使用:com.mysql.jdbc.Driver
MYSQL8驱动类使用:com.mysql.cj.jdbc.Driver
2.连接地址url
MYSQL5:"jdbc:mysql://localhost:3306/数据库名
MYSQL8:"jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
- 创建数据库表User,实体类User;-
- 创建mapper接口:相当于dao,但只需要创建mapper接口,不需要提供实现类,接口命名要和数据库表、实体类名称一一对应即UserMapper;
-
public interface UserMapper {int insertUser (); }
- 创建mybatis映射文件;
命名规则:表对应的实体类类名 + Mapper.xml
- 一个映射文件对应一个实体类,对应一张表的操作;
- Mybatis的映射文件用于编写SQL,访问及操作表中的数据;
- 映射文件存放的位置是:src/main/resources/mappers目录下
mapper接口和映射文件要保持两个一致:
- mapper接口的全类名要和映射文件的namespace一致;
- mapper接口中的方法的方法名要和映射文件中的sql的id保持一致;
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fz.mybatis.mapper.UserMapper"><!--int insertUser ()--><insert id="insertUser">insert into user values (null,"admin","123456",23,"男","123456@qq.com")</insert>
</mapper>
- 在mybaits核心配置文件中引入mybatis映射文件
<!--引入mybatis的映射文件-->
<mappers><mapper resource="mappers/UserMapper.xml"/>
</mappers>
创建测试文件
使用myBatis提供的操作数据库的对象sqlSession通过调用getMapper方法创建mapper接口的代理实现类,直接调用mapper接口中的方法,从而定位到sql语句执行;
public class MybatisTest {@Testpublic void testInsert () throws IOException {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// 获取sqlSessionFactoryBuilderSqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 获取sqlSessionFactorySqlSessionFactory build = sqlSessionFactoryBuilder.build(is);// 获取sql的会话对象sqlSession,是myBatis提供的操作数据库的对象SqlSession sqlSession = build.openSession();// 获取UserMapper的代理实现类的对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 调用mapper接口中的方法int result = mapper.insertUser();// 提交事务sqlSession.commit();System.out.println(result);// 关闭sqlSession对象sqlSession.close();}
}
或创建mapper接口的代理实现类对象,重写接口中的抽象方法,通过mapper接口的全类名找到映射文件,通过mapper接口中的方法找到sql语句执行
int result = sqlSession.insert("com.fz.mybatis.mapper.UserMapper.insertUser");
加入log4j日志功能:
首先加入依赖
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version>
</dependency>
配置log4j配置文件
<?xml version="1.0" encoding="UTF-8"?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8"/><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n"/></layout></appender><logger name="java.sql"><level value="debug"/></logger><logger name="org.apache.ibatis"><level value="info"/></logger><root><level value="debug"/><appender-ref ref="STDOUT"/></root>
</log4j:configuration>
查询功能
需要在select标签上设置resultType和reaultMap
resultType:设置结果类型,即查询的数据需要转换的java类型;
reaultMap:自定义类型,处理多对一或一对多二点映射关系;
public interface UserMapper {User getUserById ();
}
<select id="getUserById" resultType="com.fz.mybatis.pojo.User" >select * from user where id = 1
</select>
@Test
public void testSelect () {try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserById();System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
}
二.Mybatis核心配置文件解析
1.environment标签
environments标签:设置连接数据库的环境,属性default设置默认使用的环境的id,在该标签内部使用environment标签设置具体的环境;
environment标签:设置一个具体的连接数据库的环境,属性id用来设置环境的唯一标识不能重复。每一个环境又分为两个子标签
- transcationManger用来设置事务的管理方式,属性type设置事务管理的方式,值为JDBC(使用JDBC原生的事务管理的方式即自动提交事务可以手动的开启和关闭,或手动提交事务或回滚事务)和MANAGED(被管理的,例如spring整合Mybatis时其事务管理交由spring管理)
- dataSource用于设置数据源,属性type用于设置数据源的类型,一共三个值(POOLED|UNPOOLED|JNDI)
POOLED:表示使用数据库连接池
UNPOOLED:表示不适用数据库连接池,即每一个获取连接都需要重新创建一个链接
JNDI:表示使用上下文中的数据源
<!--配置连接数据库的环境--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments>
此外在核心配置文件中使用properties标签,其resource属性指定外部的properties文件路径,引入properties配置文件,此后可以在当前文件中使用${key}的方式访问配置文件中的value;
2.typeAliases
Mybatis核心配置文件中的标签必须要使用指定的顺序去配置,顺序如下:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?, databaseIdProvider?,mappers?
typeAliases标签用于设置类型别名,即为某一个具体的类型设置一个别名,在Mybatis的范围中,就可以使用别名表示一个具体的类型;
其中的type属性用于设置取别名的类型,alias用于设置某个类型的别名,该属性可以省略不写,当设置了type属性后,就有了一个默认的别名为该类的类名且不区分大小写;
<typeAliases><typeAlias type="com.fz.mybatis.pojo.User" alias="U1"></typeAlias>
</typeAliases>
还可以通过包来设置类型的别名,指定包下的所有类型将全部拥有默认的别名;
<typeAliases><package name="com.fz.mybatis.pojo"/></typeAliases>
3.mappers
mappers标签用于引入Mybatis的映射文件。可以使用mapper标签引入一个映射文件,但往往映射文件数量较多,所以可以通过包的方式即package标签来引入,需要满足两个条件:
mapper接口和映射文件所在的包必须一致;
mapper接口的名字和映射文件的名字必须一致;
在resources目录下创建文件com/fz/mybatis/mapper,将所有映射文件放在该文件夹下,而所有的mapper接口在java文件下的com/fz/mybatis/mapper;
这样当项目打包以后形成的target文件中这连个文件就会在同一个目录下;
这样就可以在在配置文件中使用package标签映入映射文件了
<mappers><package name="com.fz.mybatis.mapper"/>
</mappers>
使用idea创建mybatis配置文件的模板:File->Settings
三.Mybatis获取参数值
Mybatis获取参数值的方式有:${}字符串拼接(手动加引号)和#{}占位符赋值(自动加引号)
1.若mapper接口获取参数的类型为单个字面量
此时可以通过${}和#{}以任意的内容获取参数值,一定要注意${}的单引号问题
示例:根据用户名查询用户信息
首先创建mapper接口
public interface UserMapper {// 根据用户名查询用户信息User getUserByUsername (String username);
}
映射文件中添加sql语句,使用任意内容username获取参数,当然也可以是其他的命名
<select id="getUserByUsername" resultType="User">select * from user where username = #{username}
</select>
创建测试类,获取用户名为admin1的用户信息
@Test
public void getUserByUsername () {try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserByUsername("admin1");System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
}
2.若mapper接口的方法的参数为多个字面量类型
此时mybatis会将参数放在map集合中,以两种方式存储数据
以arg0,arg1...为键,以参数为值
以param1,param2...为键,以参数为值
因此只需要通过#{}或${}来访问map集合的键,就可以获取对应的值
public interface UserMapper {// 用户登录User checkLoginIn (String username,String password);
}
<select id="checkLoginIn" resultType="User">select * from user where username = #{arg0} and password =#{arg1}
</select>
@Test
public void checkLoginIn () {try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.checkLoginIn("admin1","123456");System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
}
3.mapper接口方法的参数为map集合类型
因此只需要通过#{}或${}来访问map集合的键,就可以获取对应的值
public interface UserMapper {User checkLoginInMap (Map<String,String> map);
}
<select id="checkLoginInMap" resultType="User">select * from user where username = #{username} and password =#{password}
</select>
@Test
public void checkLoginInMap () {try {.......UserMapper mapper = sqlSession.getMapper(UserMapper.class);Map<String, String> map = new HashMap<>();map.put("username","admin1");map.put("password","123456");User user = mapper.checkLoginInMap(map);System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
}
4.mapper接口方法的参数为实体类类型的参数
只需要通过#{}或${}来访问实体类中的属性名,就可以获取相对应的属性值
创建实体类,实体类的属性必须要有对应的getXxx和setXxx方法,这样才能作为一个实体类的属性被Mybatis获取到,反之,如果不声明属性但存在getXxx和setXxx方法,依旧会被认作实体类中的一个属性;
public class User {private Integer id;private String username;private String password;private int age;private String gender;private String email;......
}
public interface UserMapper {void insertUserByUser (User user);
}
<insert id="insertUserByUser">insert into user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>
@Test
public void testInsertUser () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = new User(null,"root","123456",33,"女","123@qq.com");mapper.insertUserByUser(user);} catch (IOException e) {throw new RuntimeException(e);}
}
5.在mapper接口方法的参数上设置@Param注解
以@Param注解的value属性值为键,以参数为值;
以param1,param2......为键,以参数为值
只需要通过#{}或${}来访问实体类中的属性名,就可以获取相对应的属性值
示例:在mapper接口中以username和password为键,以方法的实参为值获取参数
public interface UserMapper {User checkLoginInByParam (@Param("username") String username,@Param("password") String password);
}
在mapper映射文件中使用上面声明的键放在#{}中作为占位符赋值;
<select id="checkLoginInByParam" resultType="User">select * from user where username = #{username} and password =#{password}
</select>
添加测试类:
@Test
public void checkLoginInParam () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.checkLoginInByParam("root", "123456");System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
}
四.Mybatis查询功能
若sql查询的结果为多条时,一定不能以实体类类型作为mapper接口方法的返回值,否则会抛出TooManyResultsException;
若sql查询的结果为一条时,此时可以使用实体类类型或list集合作为方法的返回值;
示例1:查询单行单列的数据,查询表中的用户总数
public interface UserMapper {// 查询用户的总数量Integer getCount ();
}
<select id="getCount" resultType="java.lang.Integer">select count(*) from user
</select>
需要将查询出的类型设置在select标签的resultType中设置类型别名,而Mybatis中为Java中常用的数据类型设置了类型别名,例如:
Integer:Integer,int
int:_int,_integer
Map:map
String:string
示例2:查询的结果没有实体类对应时,需要使用Map集合为返回的类型
查询得到的结果是一个map集合,且查询到的内容是以键值对的方式呈现的,若查询到的数据的某个字段为null,则该字段不会出现在map集合中,如下所示:
public interface UserMapper {// 根据id查询用户的信息的map集合Map<String,Object> getUserByIdToMap (@Param("id") Integer id);
}
resultType的结果为一个Map集合,可以使用Mybatis中提供的Map集合的类型别名map
<select id="getUserByIdToMap" resultType="map">select * from user where id = #{id}
</select>
查询中的结果以键值对的方式出现在返回的Map集合中
@Test
public void testGetUserByIdToMap () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);Map<String, Object> userByIdToMap = mapper.getUserByIdToMap(14);// {password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}System.out.println(userByIdToMap);} catch (IOException e) {throw new RuntimeException(e);}
}
示例3:查询的结果没有实体类对应时,将查询到的多条数据保存在集合中返回
1)将mapper接口方法的返回值设置为泛型是map的list集合
public interface UserMapper {// 获取所有用户信息List<Map<String,Object>> getAllUserByList ();
}
<select id="getAllUserByList" resultType="map">select * from user
</select>
测试代码略,最终的返回结果如下:
[{password=123456, gender=男, id=1, age=23, email=123456@qq.com, username=admin1}, {password=123456, gender=女, id=12, age=33, email=123@qq.com, username=root}, {password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}]
2)可以将每条数据转换为Map集合放在一个大的Map中,但是必须要通过@MapKey注解,在该注解中要声明是将查询的哪个字段的值作为大Map的键;
public interface UserMapper {@MapKey("id")Map<String,Object> getAllUserToMap ();
}
<select id="getAllUserToMap" resultType="map">select * from user
</select>

最终的返回结果如下:
{1={password=123456, gender=男, id=1, age=23, email=123456@qq.com, username=admin1}, 12={password=123456, gender=女, id=12, age=33, email=123@qq.com, username=root}, 14={password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}}
五.特殊的SQL执行
1.模糊查询
前置知识:使用LIKE关键字进行模糊查询,可以提高%表示任意个数的任意字符,用_表示任意的单个字符;
在Mybatis中有三种方式进行模糊查询,建议使用第三种
<select id="getUserByLike" resultType="User">select * from user where username like '%${Like}%'select * from user where username like concat('%',#{Like},'%')select * from user where username like "%"#{Like}"%"
</select>
2.批量删除
可以使用
DELETE FROM 表名 WHERE 条件1 OR 条件2
DELETE FROM 表名 WHERE 字段名 IN (XX,XX,...)
在Mybaits中使用${}的方式进行批量删除
public interface UserMapper {// 批量删除void deleteMoreUser (@Param("ids") String ids);
}
<delete id="deleteMoreUser">delete from user where id in (${ids})
</delete>
@Test
public void testDeleteMoreUser () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.deleteMoreUser("12,14");} catch (IOException e) {throw new RuntimeException(e);}
}
3.动态设置表名
必须使用${}的方式,不能使用#{},因为它是占位符赋值,会自动加单引号‘’,而sql语句中表名是不能加单引号的;
public interface UserMapper {// 动态设置表名获取查询信息List<User> getUserList (@Param("tableName") String tableName);
}
<select id="getUserList" resultType="User">select * from ${tableName}
</select>
@Test
public void testGetUserList () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userList = mapper.getUserList("user");System.out.println(userList);} catch (IOException e) {throw new RuntimeException(e);}
}
4.添加功能获取自增的主键
当向表中添加一条记录时,一般记录的主键是数据库生成的(这里设置为自增主键),往往需要立刻获取,一般会认为新添加的主键一定是当前表中值最大的主键,直接获取最大主键即可,但实际场景中会存在多个线程操作数据库的情况,所以通过主键最大值获取到的主键很可能不是我们刚刚添加的。而JDBC底层提供了获取自增主键的方式,Mybatis中也封装了JDBC中的功能;
创建mapper接口方法,由于是添加数据,返回值为空void(固定的返回值),参数类型为实体类User类型
public interface UserMapper {// 添加用户信息并获取自增的主键void insertUserAndGetKey (User user);
}
创建映射文件,需要添加两个属性:
useGeneratedKeys:表示当前添加功能使用自增的主键
keyProperty:将添加的数据的自增主键为实体类类型的参数的属性赋值
<insert id="insertUserAndGetKey" useGeneratedKeys="true" keyProperty="id">insert into user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>
由于增加记录方法的返回值固定是void类型,所以不能将主键作为方法的返回值返回,只能返回到创建的实体类中的指定属性,所以使用keyProperty属性指定返回到实体类的哪个属性中;
创建测试方法,添加记录:
@Test
public void testInsertUserAndGetKey () {try {......UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = new User(null, "蒙奇·D·路飞", "123456", 23, "男", "123456@qq.com");mapper.insertUserAndGetKey(user);System.out.println(user);} catch (IOException e) {throw new RuntimeException(e);}
此时打印实体类实参就会获取到id主键值了:
User{id=25, username='蒙奇·D·路飞', password='123456', age=23, gender='男', email='123456@qq.com'}
六.自定义映射ResultMap
使用全局配置解决数据库字段名和pojo类属性名不一致的情况。往往数据库的字段名使用下划线的方式命名,而对应Java类则以驼峰命名,字段名和属性名不一致会导致无法获取字段的值;
1.配置文件处理字段名和属性名的映射关系
1)为查询的字段设置别名,和属性名保持一致
<select id="getEmpByEmpId" resultType="Emp">select emp_id empId,emp_name empName,age,gender from emp where emp_id = #{empId}
</select>
2)当字段符合sql的要求使用_,属性符合Java的要求使用驼峰,可以在Mybatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰:
<settings><!--将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
映射文件
<select id="getEmpByEmpId" resultType="Emp">select * from emp where emp_id = #{empId}
</select>
测试代码
@Test
public void testGetEmpByEmpId () {try {......EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empByEmpId = mapper.getEmpByEmpId(1);System.out.println(empByEmpId); // Emp{empId=1, empName='张三', age=12, gender='男'}} catch (IOException e) {throw new RuntimeException(e);}
}
2.使用ResultMap自定义映射处理映射关系
1)处理一对一的映射关系
需要使用resultMap标签,用于自定义映射关系,其包括以下属性:
id:唯一标识;
type:处理映射关系的实体类的类型;
其中包含常用的子标签有:
id: 处理主键和实体类中的属性实现映射关系;
result:处理普通字段和实体类中属性的映射关系;
子标签的常用属性有:
property: 设置映射关系中属性的属性名,必须是处理实体类类型的属性名;
column:设置映射关系中的字段名,必须是sql查询出的某个字段;
随后在select标签的resultMap属性加上对应的resultMap标签的id值
<resultMap id="empResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result>
</resultMap>
<select id="getEmpByEmpId" resultMap="empResultMap">select * from emp where emp_id = #{empId}
</select>
2)处理多对一的映射关系
创建员工类Emp,每个员工都对应一个部门dept,添加有参无参构造器,getter和setter和toString方法;
public class Emp {private Integer empId;private String empName;private Integer age;private String gender;private Dept dept;
}
创建部门类Dept;
public class Dept {private Integer deptId;private String deptName;
}
1.使用级联处理
处理好字段和实体类中的哪些属性进行映射
<resultMap id="empAndDeptResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><result column="dept_id" property="dept.deptId"></result><result column="dept_name" property="dept.deptName"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">select emp.*,dept.* from emp left join dept on emp.dept_id = dept.dept_id where emp.emp_id = 1
</select>
测试方法
@Test
public void testGetEmpAndDept () {try {......EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empByEmpId = mapper.getEmpAndDept(1);// Emp{empId=1, empName='张三', age=12, gender='男', dept=Dept{deptId=1, deptName='A'}}System.out.println(empByEmpId); } catch (IOException e) {throw new RuntimeException(e);}
}
2.使用association标签
association标签用来处理多对一的映射关系,处理实体类类型的属性,该标签包含以下常用属性:
property:设置需要处理映射关系的属性的熟悉那个名;
javaType:设置要处理的属性的类型;
<resultMap id="empAndDeptResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><association property="dept" javaType="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></association>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">select emp.*,dept.* from emp left join dept on emp.dept_id = dept.dept_id where emp.emp_id = 1
</select>
3.分步查询
可以处理多对一和一对多的关系。第一步先查询员工,第二部查询员工对应的部门信息;依旧需要子ResultMap中设置association标签,但需要设置如下属性:
property:需要处理的映射关系的属性名;
select:设置分步查询的sql的唯一标识,及当前的属性值property由哪个sql查询而来;
column:设置查询出的某个字段作为分布查询sql的条件
分步查询时最好对不同的表查询创建不同的mapper接口和映射文件。首先创建emp,dept表的接口
public interface EmpMapper {// 分布查询员工及对应部门的信息1Emp getEmpAndDeptOne (@Param("empId")Integer empId);
}
public interface DeptMapper {// 分布查询员工及对应部门的信息2Dept getEmpAndDeptByStepTwo (@Param("deptId") Integer deptId);
}
创建emp映射文件进行第一步查询,分步查询需要创建accociation标签,由于第二部需要查询dept表获取部门信息dept属性的值返回给第一步查询,所以property属性值为dept类型;此外要指定下一步查询是由哪个sql完成的,需要在select属性中指定第二部查询的方法的全类名;最后第二部查询是根据部门id获取部门信息的,即查询条件是第一步查询获取员工信息中的dept_id字段,故将其作为column属性值;
<resultMap id="getEmpAndDeptOneResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><association property="dept" select="com.fz.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="dept_id"></association>
</resultMap>
<select id="getEmpAndDeptOne" resultMap="getEmpAndDeptOneResultMap">select * from emp where emp_id = #{empId}
</select>
创建dept表的映射文件:
<mapper namespace="com.fz.mybatis.mapper.DeptMapper"><select id="getEmpAndDeptByStepTwo" resultType="Dept">select * from dept where dept_id = #{deptId}</select>
</mapper>
创建测试类:注意此时需要将核心配置文件中添加settings标签将下划线映射为驼峰,这样才能获取dept属性的对象值
@Test
public void testGetEmpAndDeptByStep () {try {......EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empByEmpId = mapper.getEmpAndDeptOne(1);// Emp{empId=1, empName='张三', age=12, gender='男', dept=Dept{deptId=1, deptName='A'}}System.out.println(empByEmpId); } catch (IOException e) {throw new RuntimeException(e);}
}
分步查询的优势?
可以实现延迟加载,需要开启延迟加载功能,在全局配置文件中做如下添加:
<settings><!--开启延迟加载--><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>
在上述的mybaits-config文件中延迟加载的配置会对当前项目中所有的分布查询生效,此时如果需要对某一个分布查询进行完整加载,需要在当前的association标签加载fetchType属性,值为eager(立即加载),此外还有值lazy(延迟加载)
<association property="dept" select="com.fz.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="dept_id" fetchType="eager">
</association>
3)处理一对多的映射关系
查询部门信息时,一个部门对应多个员工,即一对多的映射关系,使用多表联查获取员工字段需要将员工信息放在一个集合中;
1.Collection方式
设置实体类属性,由于是一对多,员工信息为一个集合类型
public class Dept {private Integer deptId;private String deptName;private List<Emp> emps;
}
在ResultMap标签中添加Collection标签 ,其作用是处理一对多的映射关系(处理集合类型的属性),其中ofType属性用来设置集合类属性中存储数据的类型;
<resultMap id="deptAndEmpResultMap" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><collection property="emps" ofType="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result></collection>
</resultMap>
<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">select * from dept left join emp on dept.dept_id = emp.dept_id where dept.dept_id = #{deptId}
</select>
@Test
public void testGetDeptAndEmpByDeptId () {try {......DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept deptAndEmpByDeptId = mapper.getDeptAndEmpByDeptId(1);// Dept{deptId=1, deptName='A', emps=[Emp{empId=1, empName='张三', age=12, gender='男', dept=null}, Emp{empId=4, empName='赵六', age=16, gender='男', dept=null}]}System.out.println(deptAndEmpByDeptId); } catch (IOException e) {throw new RuntimeException(e);}
}
2.分步查询
设置部门mapper接口和映射文件
public interface DeptMapper {// 获取部门及其员工信息第一步Dept getDeptAndEmpByStepOne (@Param("deptId") Integer deptId);
}
<resultMap id="deptAndEmpResultMapByStep" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><collection property="emps" select="com.fz.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" column="dept_id"></collection>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">select * from dept where dept_id = #{deptId}
</select>
设置员工mapper接口和映射文件
public interface EmpMapper {// 获取部门及其员工信息第二步List<Emp> getDeptAndEmpByStepTwo (@Param("deptId")Integer deptId);
}
<select id="getDeptAndEmpByStepTwo" resultType="Emp">select * from emp where dept_id = #{deptId}
</select>
测试代码
@Test
public void testGetDeptAndEmpByStep () {try {......DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept deptAndEmpByStep = mapper.getDeptAndEmpByStepOne(1);System.out.println(deptAndEmpByStep);} catch (IOException e) {throw new RuntimeException(e);}
}
总结:ResultMap有三个功能。设置字段和属性的映射关系;处理多对一的映射;处理一对多的映射。其中对一对应对象(一般是在实体类中设置对象类型的属性,使用association标签),对多对应集合(一般在实体类中设置集合类型的属性,使用collection标签)。
七.动态SQL
根据特定条件动态拼接SQL语句的功能,解决拼接SQL时的痛点问题;
1.if标签
提交表单时,文本框什么都没有输入,提交到服务器中的就是空字符串;没有向服务器中提交某个请求参数,服务器获取的是null。如果出现以上两类情况,就不需要将条件拼接到sql语句中。
此时可以使用if标签,通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到sql中)
创建mapper接口:
public interface DynamicSqlMapper {// 根据条件查询员工的信息List<Emp> getEmpByCondition (Emp emp);
}
mapper映射文件:
<mapper namespace="com.fz.mybatis.mapper.DynamicSqlMapper"><select id="getEmpByCondition" resultType="Emp">select * from emp where<if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if></select>
</mapper>
测试文件:直接传递一个实体类参数作为客户端提交的数据
@Testpublic void testGetEmpByCondition () {try {......DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Emp emp = new Emp(null,"张三",12,"男");List<Emp> empByCondition = mapper.getEmpByCondition(emp);System.out.println(empByCondition);} catch (IOException e) {throw new RuntimeException(e);}}
2.where标签
上述案例中,如果where关键字后的if标签没有一个成立,此时where关键字依旧会存在,会报错。可以在where关键字后添加横成立条件 1=1 或者使用<where>标签,它有如下的作用:
如果当前where标签内有条件成立,会自动生成where关键字;
将标签内内容前多余的and关键字去掉,但是其中内容后对于的and不能去掉;
where标签中没有任何一个条件成立,则where标签没有任何功能;
对上述映射文件做如下修改:
<select id="getEmpByCondition" resultType="Emp">select * from emp<where><if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if></where>
</select>
3.trim标签
用于截取,可以在标签中内容的前面或后面天添加指定内容或去掉指定内容,其中有如下常用属性:
prefix,suffix:在标签中内容的前面或后面添加指定内容;
prefixOverrides,suffixOverrides:在标签中内容的前面或后面去掉指定内容;
<select id="getEmpByCondition" resultType="Emp">select * from emp<trim prefix="where" suffixOverrides="and"><if test="empName != null and empName != ''">emp_name = #{empName} and</if><if test="age != null and age != ''">age = #{age} and</if><if test="gender != null and gender != ''">gender = #{gender}</if></trim>
</select>
例如添加了如下的测试代码,此时设置性别gender字段传入值为空:
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
Emp emp = new Emp(null,"张三",12,"");
List<Emp> empByCondition = mapper.getEmpByCondition(emp);
System.out.println(empByCondition);
此时生成的sql语句如:select * from emp where emp_name = ? and age = ?
4.choose,when,otherwise标签
相当于java中的if...else if...else,when至少设置一个,otherwise最多设置一个(它标识else)
mapper接口:
public interface DynamicSqlMapper {// 使用choose查询员工信息List<Emp> getEmpByChoose (Emp emp);
}
映射文件:
<select id="getEmpByChoose" resultType="Emp">select * from emp<where><choose><when test="empName != null and empName != ''">emp_Name = #{empName}</when><when test="age != null and age != ''">age = #{age}</when><when test="gender != null and gender != ''">gender = #{gender} </when></choose></where>
</select>
测试代码:
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
Emp emp = new Emp(null,"张三",12,"男");
List<Emp> empByCondition = mapper.getEmpByChoose(emp);
System.out.println(empByCondition);
此时生成的sql语句是:select * from emp WHERE emp_Name = ? ,显然满足if...else的逻辑,当员工姓名emp_Name符合条件时,其后面的所有条件都不会判断,所以sql语句中只有emp_Name = ?。
5.foreach标签
用于批量操作,常用的有批量添加和批量删除。
示例1:批量添加用户
mapper接口设置形参为List类型,此时Mybatis会将其以list为键,参数值为值放在一个Map中;如果形参为Array类型,此时Mybatis会将其以array为键,参数值为值放在一个Map中;为了简单起见,在参数前添加@Param注解;
public interface DynamicSqlMapper {// 批量添加员工信息void insertMoreEmp(@Param("emps") List<Emp> emps);
}
映射文件,使用sql语句insert into emp values (),(),()的方式添加多条记录,使用foreach标签循环小括号遍历集合emps ,使用item="emp"表示集合内的每一个员工信息,使用separator添加每一次循环括号之间的分隔符" , ",如下:
<insert id="insertMoreEmp">insert into emp values<!--emp表示集合中的每一个员工信息,所以访问员工属性需要使用emp.xxx的方式--><foreach collection="emps" item="emp" separator=",">(null,#{emp.empName},#{emp.age},#{emp.gender},null)</foreach>
</insert>
测试代码:
@Testpublic void testInsertMoreEmp () {try {......DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Emp emp1 = new Emp(null,"宇智波止水",12,"男");Emp emp2 = new Emp(null,"宇智波带土",12,"男");Emp emp3 = new Emp(null,"宇智波鼬",12,"男");List<Emp> list = Arrays.asList(emp1, emp2, emp3);mapper.insertMoreEmp(list);} catch (IOException e) {throw new RuntimeException(e);}}
示例2:批量删除用户
设置mapper接口
public interface DynamicSqlMapper {// 批量删除员工void deleteMoreEmp(@Param("empIds") Integer[] empIds);
}
设置映射文件,使用delete from emp where emp_id in (1,2,3)删除指定的多条记录,在()中使用foreach标签遍历数组中的empId,open和close设置当前循环以什么开始和结束;
<delete id="deleteMoreEmp">delete from emp where emp_id in<foreach collection="empIds" item="empId" separator="," open="(" close=")">#{empId}</foreach>
</delete>
或使用 delete from emp where emp_id = 1 or 2 or 3的方式删除多条记录
<delete id="deleteMoreEmp">delete from emp where<foreach collection="empIds" item="empId" separator="or">emp_id = #{empId}</foreach>
</delete>
测试代码如下:
@Test
public void testDeleteMoreEmp () {try {......DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Integer[] empIds = new Integer[]{5,6,7};mapper.deleteMoreEmp(empIds);} catch (IOException e) {throw new RuntimeException(e);}
}
总结:foreach标签常用属性
collection:设置要循环的数组或集合
item:用一个字符串表示数组或集合中的每一个数据;
separator:设置每次循环的数组之间的分隔符;
open:循环所有内容以什么开始;
close:循环的所有内容以什么结束;
6.sql标签
sql标签将常用的sql片段进行记录,随后就可以在需要使用的地方使用include标签进行引用。
<sql id="empColumns">emp_id,emp_name,age,gender,dept_id
</sql>
<select id="getEmpByCondition" resultType="Emp">select <include refid="empColumns"></include> from emp
</select>
7.set标签
<set>标签可以帮助我们生成set关键字,去除字段后多余的。
<update id="xxx">update xxx<set><if test="username != null and username != ''">username=#{username},</if><if test="password!= null and password!= ''">password=#{password},</if></set>where id=#{id}
</update>
八.Mybatis缓存
Mybitis缓存主要针对的都是查询功能,可以将查询出的数据进行缓存,重复的数据再次查询就可以从缓存中获取。分为一级缓存和二级缓存;
1.一级缓存
默认开启的缓存级别。Mybatis的一级缓存是sqlSession级别的,及通过同一个SqlSession查询的数据会被缓存,再次使用同一个SqlSession查询同一条数据会从缓存中获取;
一级失效的四种情况:
不同的SqlSession对应不同的一级缓存;
同一个SqlSession但查询条件不同;
同一个SqlSession两次查询期间执行了任何一次增删改操作;
同一个SqlSession两次查询期间手动清空了缓存;
2.Mybatis二级缓存
Mybatis的二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory所获取的SqlSession对象查询的数据会被缓存,再次通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取;
Mybatis二级缓存开启的条件:
- 在核心配置文件,设置全局配置属性cacheEnabled=“true”,默认为true所以不需要设置;
- 在映射文件中设置标签<cache/>;
- 二级缓存必须存在SqlSession关闭或提交之后才会生效,即执行sqlSession.close()方法;
- 查询的数据所转换的实体类类型必须实现序列化的接口,即public class Xxx implements Serializable
二级缓存失效的条件:
两次查询之间执行任意的增删改,会使一级二级缓存同时失效(手动清空缓存不会使二级缓存失效);
Mybatis缓存查询的顺序:
- 先查询二级缓存,因为二级缓存中可能有其他程序已经查出的数据,可以直接拿来使用;
- 如果二级缓存没命中,再查询一级缓存;
- 如果一级缓存也没有命中,则查询数据库;
- SqlSession关闭以后,一级缓存中的数据会写入二级缓存;