目录
一.搭建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 configuration
PUBLIC "-//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 mapper
PUBLIC "-//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 {
@Test
public void testInsert () throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 获取sqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 获取sqlSessionFactory
SqlSessionFactory 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 {
// 分布查询员工及对应部门的信息1
Emp getEmpAndDeptOne (@Param("empId")Integer empId);
}
public interface DeptMapper {
// 分布查询员工及对应部门的信息2
Dept 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>
测试文件:直接传递一个实体类参数作为客户端提交的数据
@Test
public 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>
测试代码:
@Test
public 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关闭以后,一级缓存中的数据会写入二级缓存;