Java之MyBatis
ORM介绍
- 对象:Java实体对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段 |
对象 | 记录/行 |
背景
原始JDBC操作问题分析
1、频繁创建和销毁数据库的连接会造成系统的资源浪费从而影响系统性能;
2、sql硬编码,如需修改,则需改动java代码,不易维护;
3、查询操作,结果集与实体需要手动映射,较为繁琐;
4、增删改操作涉及到的参数需要,手动设置并处理占位符问题。
原始JDBC问题解决方案
1、使用数据库连接池初始化连接资源;
2、SQL语句抽取到配置文件中;
3、通过反射、内省等底层技术,将实体与字段自动映射。
MyBatis框架
介绍
- MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接、创建执行者对象等复杂繁琐的操作;
- MyBatis通过xml或注解的方式将要执行的的各种Statement配置起来,通过Java对象和Statement中的SQL动态参数进行映射生成最终要执行的SQL语句;
- 最后MyBatis框架执行完SQL并将结果映射为Java对象并进行返回,通过ORM思想解决实体与数据库字段映射问题,对JDBC封装,屏蔽了JDBC原生的API底层访问细节,避免直接和底层的JDBC API打交道;
快速入门
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>MyBatisDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--MyBatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
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核心根标签-->
<configuration>
<properties resource="jdbc.properties"/>
<!--数据库连接环境,可通过environment子标签来配置多个环境,default来指定使用的是哪个环境-->
<environments default="development">
<!--id唯一标识-->
<environment id="development">
<!--事务管理,采用JDBC默认事务-->
<transactionManager type="JDBC"/>
<!--数据源,数据库连接池-->
<dataSource type="POOLED">
<!-- 连接数据库四要素 -->
<property name="driver" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射配置文件 -->
<mappers>
<!--扫描指定包下的mapper.xml文件-->
<!--<package name="com.test.mapper"/>-->
<!--指定配置文件的名称-->
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
jdbc.properties(数据库连接配置)
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
实体类
package com.test.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* 映射数据库中的t_user表
*
* @author zhangzengxiu
* @date 2022/9/3
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
private static final long serialVersionUID = -2427372682522927866L;
/**
* 主键id
*/
private Integer id;
/**
* 用户名
*/
private String usetname;
/**
* 密码
*/
private String password;
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
/**
* 邮箱
*/
private String email;
}
数据库表
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(30) DEFAULT NULL COMMENT '密码',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` varchar(2) DEFAULT NULL COMMENT '性别',
`email` varchar(30) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mapper接口
package com.test.mapper;
import com.test.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author zhangzengxiu
* @date 2022/9/3
*/
public interface UserMapper {
/**
* 新增user
*
* @param user
* @return
*/
int insertUser(User user);
/**
* 根据id删除user
*
* @param id
* @return
*/
int delUserById(@Param("id") Integer id);
/**
* 更新user
*
* @param user
* @return
*/
int updateUser(User user);
/**
* 查询user
*
* @param id
* @return
*/
User queryUser(@Param("id") Integer id);
/**
* 查询user列表
*
* @return
*/
List<User> queryUserList();
}
mapper.xml(映射配置文件)
<?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接口对应-->
<mapper namespace="com.test.mapper.UserMapper">
<!--添加user-->
<insert id="insertUser" parameterType="com.test.pojo.User">
INSERT INTO t_user
(
`username`,
password,
age,
sex,
email
)
VALUES
(
#{username},
#{password},
#{age},
#{sex},
#{email}
)
</insert>
<!--根据id删除user-->
<delete id="delUserById">
DELETE FROM t_user
WHERE id=#{id}
</delete>
<!--更新user-->
<update id="updateUser">
UPDATE t_user
SET username=#{username},
password=#{password},
age= #{age},
sex=#{sex},
email=#{email}
WHERE id=#{id}
</update>
<!--查询user-->
<select id="queryUser" resultType="com.test.pojo.User">
SELECT
id,
username,
password,
age,
sex,
email
FROM
t_user
WHERE
id=#{id}
</select>
<!--查询user列表-->
<select id="queryUserList" resultType="com.test.pojo.User">
SELECT
id,
username,
password,
age,
sex,
email
FROM t_user
</select>
</mapper>
mapper接口全类名—>mapper映射配置文件.xml—>SQL语句—>ORM映射
1、映射文件的namespace与mapper接口全类名保持一致
2、映射文件中的SQL语句的id与mapper接口方法名保持一致
3、查询功能标签必须设置resultType或resultMap
3.1resultType:设置默认的映射关系,java属性值与数据库字段列名完全匹配
3.2resultMap:设置自定义的映射关系,java属性与数据库字段列名不匹配情况或联合查询
单测
工具类
package com.test.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
/**
* Sqlsession工具类
*
* @author zhangzengxiu
* @date 2022/9/3
*/
public class SqlSessionUtil {
/**
* 获取Sqlsession
*
* @return
*/
public static SqlSession getSqlSession() throws Exception {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession 设置为自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
return sqlSession;
}
}
测试代码
package com.test.mapper;
import com.test.pojo.User;
import com.test.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
/**
* @author zhangzengxiu
* @date 2022/9/3
*/
public class UserMapperTest {
/**
* 测试添加user方法
*
* @throws Exception
*/
@Test
public void testInsertUser() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
/*
SqlSession底层通过代理模式
为mapper接口创建mapper接口的实现类对象并返回
*/
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User(null, "zhanghsan", "123454", 23, "男", "1234@qq.com");
int res = userMapper.insertUser(user);
Assert.assertEquals(1, res);
}
/**
* 测试删除user方法
*
* @throws Exception
*/
@Test
public void testDelUser() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.delUserById(20);
Assert.assertEquals(1, res);
}
/**
* 测试更新user方法
*
* @throws Exception
*/
@Test
public void testUpdateUser() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(22);
user.setUsername("yyccf");
user.setPassword("222");
user.setAge(30);
user.setSex("男");
user.setEmail("1222@123.com");
int res = userMapper.updateUser(user);
Assert.assertEquals(1, res);
}
/**
* 测试查询user方法
*
* @throws Exception
*/
@Test
public void testQueryUser() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.queryUser(23);
System.out.println("user = " + user);
}
/**
* 测试查询user列表方法
*
* @throws Exception
*/
@Test
public void testQueryUserList() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.queryUserList();
for (User user : users) {
System.out.println(user);
}
}
}
注意:
踩坑:
由于我们核心配置文件中配置的事务管理是JDBC,
则我们的事务都需要手动去处理,需要手动提交事务才会生效
sqlSession.commit();
或者创建sqlSession对象时,设置为自动提交,即可不用手动提交
//获取SqlSession 设置为true自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);小技巧:
想查看实际执行的SQL语句:
通过debug打断点到org.apache.ibatis.executor.SimpleExecutor类
- 增删改类方法:debug断点到doUpdate()
- 查询方法:debug断点doQuery
补充:
SqlSession:Java程序与数据库之间的会话;由MyBatis为我们提供的操作数据库的会话对象
SqlSessionFactory:生产sqlSession工厂;
优化
事务管理
设置事务为自动提交,不手动控制事务
JDBC事务默认自动提交,可以通过手动事务管理
connection.setAutoCommit(false);
MyBatis采用JDBC事务管理器,默认所有事物的提交或回滚都需要手动处理
JDBC事务默认自动提交:
MyBatis采用JDBC事务管理器,sqlSession默认不自动提交:
核心代码
//事务管理
//获取SqlSession 默认为false,需要手动控制事务,提交事务等
SqlSession sqlSession = sqlSessionFactory.openSession();
//手动提交事务
sqlSession.commit();
//获取SqlSession 设置为自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//提交事务(不需要了)
//sqlSession.commit();
MyBatis日志记录
核心操作
pom依赖
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
日志文件 log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<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>
控制台打印信息
日志级别
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
从左到右内容越详细
核心配置文件
SSM(Spring+SpringMVC+MyBatis)框架该配置文件可以省略
mybatis-confuig.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核心根标签-->
<configuration>
<!--引入properties文件-->
<properties resource="jdbc.properties"/>
<!--类型别名,
ype:设置别名的类型 (按照类型设置不常用,一般是指定包)
alias可选:设置某个类型的别名。
不设置默认是类名且不区分大小写;设置了则为设置的别名且不区分大小写
-->
<typeAliases>
<!--alias可以指定固定的别名(不区分大小写)不常用!!!-->
<!--<typeAlias type="com.test.pojo.User" alias="User"/>-->
<!--不加alias,则别名默认为类名不区分大小写 不常用!!!-->
<!--<typeAlias type="com.test.pojo.User"/>-->
<!--设置指定包下所有的类默认类型别名(类名,不区分大小写) 常用!!!-->
<package name="com.test.pojo"/>
</typeAliases>
<!--
environments:配置多个连接数据库的环境,可通过environment子标签来配置多个环境
属性:
default:设置默认使用的环境
-->
<environments default="development">
<!--id唯一标识,不能重复-->
<environment id="development">
<!--事务管理,采用JDBC默认事务
type:
JDBC:表示当前环境中,执行SQL时,使用的是JDBC原生的事务管理方式,事务的提交、回滚等操作需要手动处理
MANAGED:被管理,例如Spring。Spring与MyBatis整合后,可通过Spring中的声明式事务的方式来进行管理
-->
<transactionManager type="JDBC"/>
<!--数据源,数据库连接池-->
<!--
type:设置数据源类型
POOLED: 使用数据库连接池,缓存数据库库的连接,下次可直接从缓存中进行获取
UNPOOLED:不是用数据库连接池
JNDI:使用上下文中的数据源,例如Spring管理数据源。MyBatis来使用数据源。
Spring整合MyBatis后就不需要提供数据源了,Spring本身提供的就有数据源
-->
<dataSource type="POOLED">
<!-- 连接数据库四要素 -->
<property name="driver" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射配置文件 -->
<mappers>
<!--扫描指定包下的mapper.xml文件-->
<!--
以包为单位引入映射配置文件(推荐!!!)
注意:
1、Mapper接口包所在的目录与映射文件所在的目录保持一致
2、Mapper接口要与映射文件名称保持一致 UserMapper<->UserMapper.xml
以xml文件的形式引入:
如果xml文件很多会非常麻烦,还容易遗漏
-->
<!--指定xml映射文件目录(推荐)-->
<package name="com.test.mapper"/>
<!--指定配置文件的名称(不推荐)-->
<!--<mapper resource="mappers/UserMapper.xml"/>-->
</mappers>
</configuration>
typeAliases类型别名
查询返回的实体resultType是全限定类名,较为繁琐,可通过起别名的方式进行优化
设置别名前:
<!--查询user-->
<select id="queryUser" resultType="com.test.pojo.User">
SELECT
id,
username,
password,
age,
sex,
email
FROM
t_user
WHERE
id=#{id}
</select>
设置类名别名:
注意配置顺序!!!
<!--类型别名,
ype:设置别名的类型 (按照类型设置不常用,一般是指定包)
alias可选:设置某个类型的别名。
不设置默认是类名且不区分大小写;设置了则为设置的别名且不区分大小写
-->
<typeAliases>
<!--alias可以指定固定的别名(不区分大小写)不常用!!!-->
<!--<typeAlias type="com.test.pojo.User" alias="User"/>-->
<!--不加alias,则别名默认为类名不区分大小写 不常用!!!-->
<!--<typeAlias type="com.test.pojo.User"/>-->
<!--设置指定包下所有的类默认类型别名(类名,不区分大小写) 常用!!!-->
<package name="com.test.pojo"/>
</typeAliases>
设置类型别名后:
<!--查询user-->
<select id="queryUser" resultType="user">
SELECT
id,
username,
password,
age,
sex,
email
FROM
t_user
WHERE
id=#{id}
</select>
mappers引入映射文件
与接口对应的映射配置文件必须引入到核心配置文件中
<!-- 加载映射配置文件 -->
<mappers>
<!--扫描指定包下的mapper.xml文件-->
<!--
以包为单位引入映射配置文件(推荐!!!)
注意:
1、Mapper接口包所在的目录与映射文件所在的目录保持一致
2、Mapper接口要与映射文件名称保持一致 UserMapper<->UserMapper.xml
以xml文件的形式引入:
如果xml文件很多会非常麻烦,还容易遗漏
-->
<!--指定xml映射文件目录(推荐)-->
<package name="com.test.mapper"/>
<!--指定配置文件的名称(不推荐)-->
<!--<mapper resource="mappers/UserMapper.xml"/>-->
</mappers>
MyBatis获取参数值
mapper接口中参数的值在mapper.xml映射配置文件中如何获取
${} 与#{}
${}本质就是字符串拼接,可能会造成SQL注入,单引号需要手动处理
#{}本质就是占位符赋值
MyBatis获取参数值的不同情况:
情况一:
Mapper接口方法的参数为单个的字面量类型
Mapper接口
/**
* 根据username查询User
*
* @param username
* @return
*/
User getUserByUsername(String username);
映射配置文件
<!--根据username查询user-->
<select id="getUserByUsername" resultType="user">
<!--可查询-->
<!--SELECT * FROM t_user WHERE username = #{username}-->
<!--可查询-->
<!--SELECT * FROM t_user WHERE username = #{aaa}-->
<!--可查询,需要手动拼接单引号-->
<!--SELECT * FROM t_user WHERE username ='${username}'-->
<!--可查询,需要手动拼接单引号-->
SELECT * FROM t_user WHERE username = '${aaa}'
</select>
#{}:#{username}与 #{aaa}均可查询
占位符赋值与值没关系,只与位置有关
#{}获取,可以以任意名称来获取。本质就是占位符
: {}: :{username}与 ${aaa}均可查询。本质就是字符串拼接
${}获取,可以以任意名称来获取,但是如果是字符串类型,需要手动拼接单引号否则报错
报错信息:
总结:
${} 与#{}以任意名称的字符串来获取参数值,建议使用参数名,见名知意。但是需要注意 ${} 单引号问题。
占位符赋值与值没关系,只与位置有关;
#{}来获取,可以以任意名称来获取;
${}来获取,可以以任意名称来获取,但是如果是字符串类型,需要手动拼接单引号否则报错!!!
情况二:
Mapper接口方法的参数为多个时
Mapper接口
/**
* 根据用户名密码查询user
*
* @param username
* @param pssword
* @return
*/
User login(String username, String pssword);
映射配置文件
<!--根据username和password查询User-->
<select id="login" resultType="user">
<!--SELECT * FROM t_user WHERE username=#{username} AND password =#{password}-->
<!--SELECT * FROM t_user WHERE username=#{arg0} AND password =#{arg1}-->
SELECT * FROM t_user WHERE username='${param1}' AND password ='${param2}'
</select>
-- 无法根据参数名获取到值
SELECT * FROM t_user WHERE username=#{username} AND password =#{password}
报错信息:
-- 可根据报错提示的默认名称获取到值
SELECT * FROM t_user WHERE username=#{arg0} AND password =#{arg1}
SELECT * FROM t_user WHERE username=#{param1} AND password =#{param2}
-- 组合亦可
SELECT * FROM t_user WHERE username=#{param1} AND password =#{arg1}
-- 可根据${} 获取到值,需要注意单引号问题
SELECT * FROM t_user WHERE username='${param1}' AND password ='${param2}'
总结:
MyBatis会将这些参数放在一个map集合中,以两种方式进行存储
1、以arg0、arg1…为键,参数为值;
2、以param1、param2…为键,参数为值;
因此只需要通过#{}和${}以键的形式来访问值即可,同样需要 ${}注意单引号问题
情况三:
Mapper接口方法的参数为有多个,手动将参数封装为map
情况二中,是由MyBatis将参数自动封装为Map,我们可自己封装一个map,将map进行传递
接口
/**
* 根据用户名密码查询user
*
* @param map 参数封装为map
* @return
*/
User loginByMap(Map<String, Object> map);
映射配置文件
<!--根据参数封装的map查询user-->
<select id="loginByMap" resultType="user">
SELECT * FROM t_user WHERE username=#{username} AND password = #{password}
<!--${}注意单引号-->
<!--SELECT * FROM t_user WHERE username='${username}' AND password = '${password}'-->
</select>
测试代码
/**
* 侧说loginByMap方法
* 手动将参数封装为map
*
* @throws Exception
*/
@Test
public void testLoginByMap() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap();
map.put("username", "admin");
map.put("password", "123456");
User user = userMapper.loginByMap(map);
System.out.println(user);
}
只需要通过#{}和 以我们自己设计的键的形式来访问值即可,同样需要 {}以我们自己设计的键的形式来访问值即可,同样需要 以我们自己设计的键的形式来访问值即可,同样需要{}注意单引号问题
情况四:(常用)
Mapper接口方法的参数为实体类
接口访问
/**
* 新增user
*
* @param user
* @return
*/
int insertUser(User user);
映射配置文件
<!--添加user-->
<insert id="insertUser" parameterType="com.test.pojo.User">
INSERT INTO t_user
(
`username`,
password,
age,
sex,
email
)
VALUES
(
#{username},
#{password},
#{age},
#{sex},
#{email}
)
</insert>
总结:
map以键来访问,实体以属性名进行访问
属性严格意义上来讲是从setter方法上,去掉set之后,将首字母小写后获取到的
#{}以属性的方式进行访问, ${}需要注意单引号问题
情况五:(常用)
@Param命名参数形式
情况二时,我们使用MyBatis提供的默认的参数进行访问,不灵活
情况三时,我们可通过自己封装map的形式来,通过键的形式来访问,较为繁琐
接口
/**
* 根据用户名密码查询user
*
* @param username
* @param pssword
* @return
*/
User login(@Param("username") String username,@Param(value="pssword") String pssword);
映射配置文件
<!--根据username和password查询User-->
<select id="login" resultType="user">
SELECT * FROM t_user WHERE username=#{username} AND password =#{password}
</select>
总结:
@Param(“xxx”)是MyBatis以@Param的值为键,以参数为值,将其放到Map中,相当于情况二三的结合,还可以自己灵活的设置键
MyBatis存储map的时候,有两种方式:
1、以@Param的值为键,以参数为值
2、以param1、param2…为键,以参数为值
情况一:单个字面量的值,也通过@Param来解决
情况二:多参数,也可通过@Param来解决
整合下来,总归两种:1、实体类对象;2、@Param
MyBatis各种查询功能
查询一个实体对象
接口
/**
* 根据id查询user
*
* @param id
* @return
*/
User queryUserById(@Param("id") Integer id);
映射配置文件
<!--根据id查询user-->
<select id="queryUserById" resultType="com.test.pojo.User">
SELECT * FROM t_user WHERE id =#{id}
</select>
测试代码
/**
* 测试根据id查询user实体
* 1、若查询的结果只有一条,可以通过实体类对象或集合接收;
* 2、若查询的数据有多条,可以通过集合接收。一定不能通过实体类对象接收,此时会报错:TooManyResultsException
*
* @throws Exception
*/
@Test
public void testQueryUserById() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
Integer id = 22;
User user = selectMapper.queryUserById(id);
Assert.assertEquals(user.getId(), id);
}
总结:
1、查询结果只有1条时:
1.1、可通过实体类对象接收
1.2、可通过list集合接收
1.3、也可通过map接收
{password=123456, sex=男, id=22, age=30, email=1222@ 123.com, username=admin}
查询一个List集合
接口
/**
* 查询User列表
*
* @return
*/
List<User> queryUserList();
映射配置文件
<!--查询user列表-->
<select id="queryUserList" resultType="com.test.pojo.User">
SELECT * FROM t_user
</select>
测试代码
/**
* 测试查询user列表
*
* @throws Exception
*/
@Test
public void testQueryUserList() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
List<User> userList = selectMapper.queryUserList();
for (User user : userList) {
System.out.println(user);
}
}
总结:
2、查询出的数据有多条:
2.1、可以通过list集合接收
2.2、也可通过map接收
[{password=123456, sex=男, id=22, age=30, email=1222@ 123.com, username=admin}, {password=123454, sex=男, id=23, age=23, email=1234@ qq.com, username=zhanghsan}, {password=123454, sex=男, id=24, age=23, email=1234@ qq.com, username=zhanghsan}]
2.3在Mapper接口方法上添加@MapKey注解,此时可将每条数据转换为的map集合作为key,以某个字段的值作为键,放在同一个map集合中
{22={password=123456, sex=男, id=22, age=30, email=1222@ 123.com, username=admin}, 23={password=123454, sex=男, id=23, age=23, email=1234@ qq.com, username=zhanghsan}, 24={password=123454, sex=男, id=24, age=23, email=1234@ qq.com, username=zhanghsan}}
注意:一定不能通过实体对象接收,会报错:TooManyResultsException
查询单行单列
接口
/**
* 查询user的count数
*
* @return
*/
int queryUserCount();
映射配置文件
<!--查询user的count数-->
<select id="queryUserCount" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
测试代码
/**
* 测试查询user的count数
*
* @throws Exception
*/
@Test
public void testQueryUserCount() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
int count = selectMapper.queryUserCount();
System.out.println(cout);
}
MyBatis中设置了默认的类型别名:
举个例子:
java.lang.Integer-----> Integer , int
int------> _int , _integer
Map-----> map
String-----> string
查询一个Map集合
接口
/**
* 根据id查询User为map
*
* @param id
* @return
*/
Map<String, Object> queryUserByIdToMap(@Param("id") Integer id);
映射配置文件
<!--根据id查询User为map-->
<select id="queryUserByIdToMap" resultType="java.util.Map">
SELECT * FROM t_user WHERE id =#{id}
</select>
测试代码
/**
* 测试根据id查询User为Map
*
* @throws Exception
*/
@Test
public void testQueryUserByIdToMap() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
Integer id = 22;
Map<String, Object> map = selectMapper.queryUserByIdToMap(id);
System.out.println(map);
}
返回结果
{password=123456, sex=男, id=22, age=30, email=1222@123.com, username=admin}
查询出来的map集合:
以字段为键,以值为值
Map响应到浏览器就是一个json
查询列表为Map
若获取的数据没有实体类预支对应,可获取为map
接口
/**
* 查询多个user转为一个Map
*
* @return
*/
List<Map<String, Object>> queryUserListToMap();
/**
* 以id作为建,每个map作为值
*
* @return
*/
@MapKey("id")
Map<String, Object> queryUserListToMap2();
映射配置文件
<!--查询多个user转为一个Map-->
<select id="queryUserListToMap" resultType="java.util.Map">
SELECT * FROM t_user
</select>
<!--查询多个user转为一个Map,每个map作为值,key是MapKey的字段值-->
<select id="queryUserListToMap2" resultType="java.util.Map">
SELECT * FROM t_user
</select>
测试代码
/**
* 测试查询多个user转为一个Map
*
* @throws Exception
*/
@Test
public void testQueryUserListToMap() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
List<Map<String, Object>> maps = selectMapper.queryUserListToMap();
System.out.println(maps);
}
/**
* 测试查询多个user转为一个Map
* id为键,字段为值
*
* @throws Exception
*/
@Test
public void testQueryUserListToMap2() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SelectMapper selectMapper = sqlSession.getMapper(SelectMapper.class);
Map<String, Object> map = selectMapper.queryUserListToMap2();
System.out.println(map);
}
结果
List的Map
[{password=123456, sex=男, id=22, age=30, email=1222@123.com, username=admin}, {password=123454, sex=男, id=23, age=23, email=1234@qq.com, username=zhanghsan}, {password=123454, sex=男, id=24, age=23, email=1234@qq.com, username=zhanghsan}]
id为键,map为值
{22={password=123456, sex=男, id=22, age=30, email=1222@123.com, username=admin}, 23={password=123454, sex=男, id=23, age=23, email=1234@qq.com, username=zhanghsan}, 24={password=123454, sex=男, id=24, age=23, email=1234@qq.com, username=zhanghsan}}
特殊SQL执行
以下几种情况,用#{} 可能会出现一些问题。
处理模糊查询
接口:
/**
* 根据username模糊匹配user
*
* @param username
* @return
*/
List<User> queryUserLikeUsername(@Param("username") String username);
测试代码:
/**
* 测试根据username模糊匹配user
*
* @throws Exception
*/
@Test
public void testQueryUserLikeUsername() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
List<User> userList = mapper.queryUserLikeUsername("z");
for (User user : userList) {
System.out.println(user);
}
}
映射配置文件:
<!--根据username模糊匹配user-->
<select id="queryUserLikeUsername" resultType="com.test.pojo.User">
<!--占位符会被识别为一个字符串的一部分,会报错-->
SELECT * FROM t_user WHERE username LIKE '%#{username}%'
</select>
报错截图:
注意:
模糊查询:#{} 需要写在一对单引号中,单引号在SQL语句中,表示一个字符串,
'%?%'会被解析为字符串的一部分,而不是一个占位符。
所以当我们给占位符进行赋值的时候,没有占位符就会报错。
解决方案一:使用${} 而不是#{}
映射配置文件:
<!--根据username模糊匹配user-->
<select id="queryUserLikeUsername" resultType="com.test.pojo.User">
SELECT * FROM t_user WHERE username LIKE '%${username}%'
</select>
解决方案二:使用concat函数进行字符串拼接
映射配置文件:
<!--根据username模糊匹配user-->
<select id="queryUserLikeUsername" resultType="com.test.pojo.User">
SELECT * FROM t_user WHERE username LIKE concat('%',#{username},'%')
</select>
解决方案三:拼接字符串
映射配置文件:
<!--根据username模糊匹配user-->
<select id="queryUserLikeUsername" resultType="com.test.pojo.User">
SELECT * FROM t_user WHERE username LIKE "%"#{username}"%"
</select>
处理批量删除
接口:
/**
* 根据id批量删除
*
* @param ids
* @return
*/
int delUsersByIds(@Param("ids") String ids);
测试代码:
/**
* 测试根据id批量删除
*
* @throws Exception
*/
@Test
public void testDelUsersByIds() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
int res = mapper.delUsersByIds("21,22,30");
System.out.println(res);
}
映射配置文件:
<!--根据id批量删除-->
<delete id="delUsersByIds">
DELETE FROM t_user WHERE id IN (#{ids})
</delete>
#{} 会自动加单引号,这样的SQL在执行过程中是不正确的,所以并不会有数据被删除,因为SDL不能被正确执行
解决方案:
映射配置文件:
<!--根据id批量删除-->
<delete id="delUsersByIds">
DELETE FROM t_user WHERE id IN (${ids})
</delete>
处理动态设置表名
接口:
/**
* 根据动态表名查询
*
* @param tableName
* @return
*/
List<User> queryUserByTableName(@Param("tableName") String tableName);
测试代码:
/**
* 吃根据动态表名的查询功能
*
* @throws Exception
*/
@Test
public void testQueryUserByTableName() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
List<User> userList = mapper.queryUserByTableName("t_user");
for (User user : userList) {
System.out.println(user);
}
}
映射配置文件
<!--根据动态表名查询-->
<select id="queryUserByTableName" resultType="com.test.pojo.User">
SELECT * FROM #{tableName}
</select>
解决方案:
<!--根据动态表名查询-->
<select id="queryUserByTableName" resultType="com.test.pojo.User">
SELECT * FROM ${tableName}
</select>
添加功能获取自增的主键
当我们使用自增主键的时候,当我们需要获取添加到数据库后的自增主键使用。
接口:
/**
* 添加User
*
* @param user
* @return
*/
int insertUser(User user);
测试代码:
/**
* 测试添加User,并返回带自增主键的user
*
* @throws Exception
*/
@Test
public void testInsertUser() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
User user = new User(null, "lisi", "121223", 33, "女", "1112@123.com");
mapper.insertUser(user);
System.out.println(user);
}
映射文件:
<!--添加user-->
<!--
useGeneratedKeys:设置目前标签中的SQL使用自增的主键id;
keyProperty:将自增的主键的值,赋值给传输到映射文件中参数的某个属性,比如传给User的id属性
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user
(
`username`,
password,
age,
sex,
email
)
VALUES
(
#{username},
#{password},
#{age},
#{sex},
#{email}
)
</insert>
搭建MyBatis框架
自定义映射resultMap
如果默认java的属性名与数据库中表的列名一致,则可以实现自动映射
resultMap是如果java的属性名与实际数据库中表字段的列名不一致的解决方案,以及一对多等场景的处理。
数据准备
-- 数据准备
USE mybatis;
DROP TABLE IF EXISTS t_emp;
-- 创建员工表
CREATE TABLE IF NOT EXISTS t_emp(
eid INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键' ,
emp_name VARCHAR(20) COMMENT '员工姓名',
age INT COMMENT '年龄',
sex CHAR(1) COMMENT '性别',
email VARCHAR(20) COMMENT '邮箱',
did INT COMMENT '部门id'
);
-- 添加测试数据
INSERT INTO t_emp VALUES
(null,'zhangsan',20,'男','1234@12.com',1),
(null,'lisi',21,'男','1234@12.com',1),
(null,'wangwu',22,'男','1234@12.com',1),
(null,'zhaoliu',23,'女','1234@12.com',1),
(null,'maqi',24,'女','1234@12.com',1);
-- 创建部门表
DROP TABLE IF EXISTS t_dept;
CREATE TABLE IF NOT EXISTS t_dept(
did INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键' ,
dept_name VARCHAR(20) COMMENT '部门名称'
);
-- 添加部门测试数据
INSERT INTO t_dept VALUES
(null,'A'),
(null,'B'),
(null,'C');
背景
接口:
/**
* 查询emp列表
*
* @return
*/
List<Emp> queryEmpList();
测试代码:
/**
* 测试查询emp列表
*
* @throws Exception
*/
@Test
public void testQueryEmpList() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.queryEmpList();
for (Emp emp : emps) {
System.out.println(emp);
}
}
映射配置文件
<!--查询emp列表-->
<select id="queryEmpList" resultType="com.test.pojo.Emp">
SELECT * FROM t_emp
</select>
查询结果
中间有一列值为null,但是查询并没报错。
解决方案一:为查询出来的字段设置别名:
映射配置文件:
<!--查询emp列表-->
<select id="queryEmpList" resultType="com.test.pojo.Emp">
SELECT
eid,
emp_name AS empName,
age,
sex,
email
FROM
t_emp
</select>
解决方案二:全局配置:下划线映射为驼峰
字段必须满字段的规则,属性必须符合属性规则
MyBatis核心配置文件
<!--MyBatis全局配置-->
<settings>
<!--将_下划线映射为驼峰,举例:emp_name:empName-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
映射配置文件
<!--查询emp列表-->
<select id="queryEmpList" resultType="com.test.pojo.Emp">
SELECT
*
FROM
t_emp
</select>
映射配置文件(别名+下划线转驼峰组合)
<select id="queryEmpList" resultType="com.test.pojo.Emp">
SELECT
eid,
emp_name AS empName,
age,
sex,
email
FROM
t_emp
</select>
也可映射到数据
解决方案三:resuleMap自定义映射
resultType只能用来解决,字段名和属性名一致的情况。
用了resultMap建议把所有的映射关系全部设置出来。
映射配置文件
<resultMap id="empResultMap" type="com.test.pojo.Emp">
<!--映射主键id-->
<id property="eid" column="eid"/>
<!--映射其余属性与字段的对应关系-->
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<!--查询emp列表-->
<select id="queryEmpList" resultMap="empResultMap">
SELECT
*
FROM
t_emp
</select>
resultMap:设置自定义映射关系
id:resultMap的唯一标识
type:设置映射关系中的实体类型
子标签
id:设置主键的映射关系
result:设置普通字段的映射关系
属性:
property:设置映射关系中的属性名,必须是type属性所设置的实体类型的属性名
column:设置映射关系中的字段名,必须是SQL语句查询出的字段名
多对一映射
当表与表之间存在关系时,实体与实体之间也是存在关系的。
package com.test.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 员工表实体
*
* @author zhangzengxiu
* @date 2022/9/11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Emp {
/**
* 主键
*/
private Integer eid;
/**
* 员工姓名
*/
private String empName;
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
/**
* 邮箱
*/
private String email;
/**
* 部门
*/
private Dept dept;
}
接口
/**
* 查询员工和部门
*
* @param eid
* @return
*/
Emp queryEmpAndDeptByEid(@Param("eid") Integer eid);
测试代码:
/**
* 测试查询emp员工和部门
*
* @throws Exception
*/
@Test
public void testQueryEmpAndDeptByEid() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.queryEmpAndDeptByEid(2);
System.out.println(emp);
}
方式一:级联属性赋值
映射配置文件:
<resultMap id="empResultMapOne" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<!--级联属性映射-->
<result property="dept.did" column="did"/>
<result property="dept.deptName" column="dept_name"/>
</resultMap>
<!--查询员工和部门-->
<select id="queryEmpAndDeptByEid" resultMap="empResultMapOne">
SELECT
*
FROM
t_emp AS te
LEFT JOIN
t_dept AS td
ON
te.did = td.did
WHERE
te.eid =#{eid}
</select>
执行结果:
Emp(eid=2, empName=lisi, age=21, sex=男, email=1234@12.com, dept=Dept(did=1, deptName=A))
方式二:association
映射配置文件
<resultMap id="empResultMapTwo" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<association property="dept" javaType="com.test.pojo.Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
<!--查询员工和部门-->
<select id="queryEmpAndDeptByEid" resultMap="empResultMapTwo">
SELECT
*
FROM
t_emp AS te
LEFT JOIN
t_dept AS td
ON
te.did = td.did
WHERE
te.eid =#{eid}
</select>
association:处理多对一映射关系
property:需要处理多对一映射关系的属性名(是属性名不是类型别名)
javaType:该属性的类型
有了类型便可以通过反射来获取类型中的属性,将查询出来的字段的值,赋值给我们制定的属性。
方式三:分步查询
第一步:
接口(EmpMapper.java)
/**
* 分步查询:
* 第一步:查询员工信息,获取部门的did
* @param eid
* @return
*/
Emp queryEmpAndDeptByStepOne(@Param("eid") Integer eid);
映射配置文件:
<resultMap id="empAndStepResultMap" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<!--
select:用来设置分步查询的sql的唯一标识。(namespace.SQL的id,或者是方法的全限定类名.方法名)
column:设置分步查询的条件
propertiy:就是实体类中的属性
-->
<association property="dept"
select="com.test.mapper.DeptMapper.queryEmpAndDeptByStepTwo"
column="did">
</association>
</resultMap>
<select id="queryEmpAndDeptByStepOne" resultMap="empAndStepResultMap">
SELECT
*
FROM
t_emp
WHERE
eid=#{eid}
</select>
第二步:
接口(DeptMapper.java)
/**
* 分步查询:
* 第二步:根据did查询员工所对应的部门
*
* @param did
* @return
*/
Dept queryEmpAndDeptByStepTwo(@Param("did") Integer did);
映射配置文件
<resultMap id="deptresultMap" type="com.test.pojo.Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
</resultMap>
<!--根据did查询员工所对应的部门-->
<select id="queryEmpAndDeptByStepTwo" resultMap="deptresultMap">
SELECT
*
FROM
t_dept
WHERE
did=#{did}
</select>
测试代码
/**
* 测试分步查询
* 多对一
*
* @throws Exception
*/
@Test
public void testQueryEmpAndDeptByStepOne() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.queryEmpAndDeptByStepOne(2);
System.out.println(emp);
}
执行结果:
是分两步来执行的
延迟加载
分步查询的好处:延迟加载
延迟加载MyBatis中默认是不开启的。但是必须在全局配置文中设置全局配置信息:
lazyLoadingEnabled:延迟加载的全局开关,当开启时,所有关联的对象都会延迟加载;
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性,否则,,每个属性会按需加载。
配置:
lazyLoadingEnabled:trueaggressiveLazyLoading:false
核心配置文件
<settings>
<!--开启懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
当开启延迟加载的时候。上述分步查询时:
如果只查询emp的相关信息,则只会执行一条SQL。
但是,如果要查询员工的部门信息的时候,则会执行两条SQL。
例如:查询员工的信息和部门的信息
开启延迟:SQL是一条一条执行的,而不是全部执行完毕后再获取我们需要的数据。
不开启延迟:SQL一块儿执行,执行完成后,才能获取我们要的数据。
配置了全部延迟加载后,通过fetchType来单独控制延迟加载和立即加载:
<!--
select:用来设置分步查询的sql的唯一标识。(namespace.SQL的id,或者是方法的全限定类名.方法名)
column:设置分步查询的条件
propertiy:就是实体类中的属性
fetchType:当开启全局的延迟加载后,可通过这个属性来单独控制。
lazy:延迟加载
eager:立即加载
如果没配置全局延迟加载的开关,这两个是一样的效果,都是立即加载
-->
<association property="dept"
select="com.test.mapper.DeptMapper.queryEmpAndDeptByStepTwo"
column="did"
fetchType="lazy" >
</association>
一对多映射
多对一实体:(对应对象)
package com.test.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 员工表实体
*
* @author zhangzengxiu
* @date 2022/9/11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Emp {
/**
* 主键
*/
private Integer eid;
/**
* 员工姓名
*/
private String empName;
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
/**
* 邮箱
*/
private String email;
/**
* 部门
*/
private Dept dept;
}
一对多实体:(对应集合)
package com.test.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.List;
/**
* 部门表实体
*
* @author zhangzengxiu
* @date 2022/9/11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Dept {
/**
* 主键
*/
private Integer did;
/**
* 部门名称
*/
private String deptName;
/**
* 多个员工
*/
private List<Emp> emps;
}
接口
/**
* 根据did查询部门及员工的信息
*
* @param did
* @return
*/
Dept queryDeptAndEmp(@Param("did") Integer did);
测试代码
/**
* 测试根据did查询部门及员工的信息
*/
@Test
public void testQueryDeptAndEmp() throws Exception{
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.queryDeptAndEmp(1);
System.out.println(dept);
}
方式一:collection
映射配置文件
<resultMap id="deptAndEmpResultMap" type="com.test.pojo.Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<!--
collection:处理一对多的映射关系
ofType:集合中的存储的数据类型。List<Emp> 存的是Emp类型
-->
<collection property="emps" ofType="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</collection>
</resultMap>
<!--根据did查询部门及员工的信息-->
<select id="queryDeptAndEmp" resultMap="deptAndEmpResultMap">
SELECT
*
FROM
t_dept AS td
LEFT JOIN
t_emp AS te
ON
td.did = te.did
WHERE
td.did=#{did}
</select>
查询结果
Dept(did=1, deptName=A, emps=[Emp(eid=1, empName=zhangsan, age=20, sex=男, email=1234@12.com, dept=null), Emp(eid=2, empName=lisi, age=21, sex=男, email=1234@12.com, dept=null), Emp(eid=3, empName=wangwu, age=22, sex=男, email=1234@12.com, dept=null), Emp(eid=4, empName=zhaoliu, age=23, sex=女, email=1234@12.com, dept=null), Emp(eid=5, empName=maqi, age=24, sex=女, email=1234@12.com, dept=null)])
方式二:分步查询
接口
DeptMapper.xml
/**
* 根据did查询部门及员工的信息,分步查询
* 第一步:查询部门信息
*
* @param did
* @return
*/
Dept queryDeptAndEmpByStepOne(@Param("did") Integer did);
EmpMapper.xml
/**
* 根据did查询部门及员工的信息,分步查询
* 第二步:查询员工信息
* 查询出来的结果为 List<Emp> emps 赋值
* 所以查询出来的结果一定是集合
* </>
*
* @param did
* @return
*/
List<Emp> queryDeptAndEmpByStepTwo(@Param("did") Integer did);
测试代码
/**
* 分步查询:
* 查询部门和员工信息
*
* @throws Exception
*/
@Test
public void testQueryDeptAndEmpByStepOne() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.queryDeptAndEmpByStepOne(1);
System.out.println(dept);
}
映射配置文件
DeptMapper.xml
<resultMap id="deptAndEmpResultMapStepOne" type="com.test.pojo.Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<!--
分步查询条件:did(部门的did)
根据部门的did查询对应的员工信息
所以查询条件是部门的did
-->
<collection property="emps"
select="com.test.mapper.EmpMapper.queryDeptAndEmpByStepTwo"
column="did"></collection>
</resultMap>
<select id="queryDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapStepOne">
SELECT
*
FROM
t_dept AS td
WHERE
td.did=#{did}
</select>
EmpMapper.xml
<resultMap id="empResultMap" type="com.test.pojo.Emp">
<!--映射主键id-->
<id property="eid" column="eid"/>
<!--映射其余属性与字段的对应关系-->
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<!--分步查询第二步-->
<select id="queryDeptAndEmpByStepTwo" resultMap="empResultMap">
SELECT
*
FROM
t_emp
WHERE
did=#{did}
</select>
动态SQL
根据特定条件动态拼接SQL语句的功能,解决了拼接SQL语句字符串的痛点。
动态SQL主要是动态拼接关键字和条件。
if标签
接口
/**
* 根据条件查询员工信息
* 动态SQL参数
*
* @param emp
* @return
*/
List<Emp> queryEmpByCondition(Emp emp);
测试代码
/**
* 测试动态SQL的id标签
*
* @throws Exception
*/
@Test
public void testQueryEmpByCondition() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Emp emp = new Emp(null, "zhangsan", 20, "男", "1234@12.com", null);
List<Emp> emps = mapper.queryEmpByCondition(emp);
for (Emp emp1 : emps) {
System.out.println(emp1);
}
}
映射配置文件
<resultMap id="empResultMap" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<!--根据条件查询员工信息-->
<select id="queryEmpByCondition" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<!--
此处使用where关键字的时候,会有问题,
当第一个条件不成立,后面会直接变为: where and age=#{age} 报错
解决方案一:where 后加 1=1 即 where 1=1 恒成立,
也不会在所有条件都不满足时,不会因为多出where关键字报错。
但是!!!如果第一个条件就满足就会报错, where 1=1 emp_name=#{empName}
-->
where 1=1
<if test="empName!=null and empName!='' ">
emp_name = #{empName}
</if>
<if test="age!=null and age!='' ">
AND age = #{age}
</if>
<if test="sex!=null and sex!='' ">
AND sex = #{sex}
</if>
<if test="email!=null and email!='' ">
AND email = #{email}
</if>
</select>
目前存在的问题:where关键字,如果后面,没有条件成立会多where关键字出来,如果后面第一个条件都不成立,会where 后直接跟and 也会报错。
目前的解决方案:使用恒等式 1=1的方式来解决
但是!!!如果第一个条件就满足就会报错, where 1=1 emp_name=#{empName}
where标签
动态生成where关键字。能动态添加where 关键字还能将内容前多余的AND或OR去掉;
以及在全部条件都不满足的情况下,能动态去掉where关键字。
使用where标签的SQL
映射配置文件:
<!--根据条件查询员工信息-->
<select id="queryEmpByCondition" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<where>
<if test="empName!=null and empName!='' ">
emp_name = #{empName}
</if>
<if test="age!=null and age!='' ">
AND age = #{age}
</if>
<if test="sex!=null and sex!='' ">
AND sex = #{sex}
</if>
<if test="email!=null and email!='' ">
AND email = #{email}
</if>
</where>
</select>
注意:where关键字不能将内容后的AND或者OR去掉,只能将内容前的去掉。后面的有多余的时,会报错。
trim标签
若标签中有内容时:
prefix、suffix:将trim标签中的内容前面或后面添加指定内容
prefixOverrides、suffixOverrides:将trim标签中的内容前面或后面去掉指定内容
若标签中没有内容时:
trim标签也没有任何效果
<!--根据条件查询员工信息-->
<select id="queryEmpByCondition" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<!--
prefix:前缀
prefixOverrides:前缀重写
suffix:后缀
suffixOverrides:后缀重写
-->
<trim prefix="where" prefixOverrides="AND|OR">
<if test="empName!=null and empName!='' ">
emp_name = #{empName}
</if>
<if test="age!=null and age!='' ">
AND age = #{age}
</if>
<if test="sex!=null and sex!='' ">
AND sex = #{sex}
</if>
<if test="email!=null and email!='' ">
AND email = #{email}
</if>
</trim>
</select>
<!--根据条件查询员工信息-->
<select id="queryEmpByCondition" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<!--
prefix:前缀
prefixOverrides:前缀重写
suffix:后缀
suffixOverrides:后缀重写
-->
<trim prefix="where" prefixOverrides="AND|OR">
<if test="empName!=null and empName!='' ">
<!--
当这个条件满足时,prefixOverrides会将条件前多余的AND会被去掉,
第一个条件最前面的AND也可手动不写
-->
AND emp_name = #{empName}
</if>
<if test="age!=null and age!='' ">
AND age = #{age}
</if>
<if test="sex!=null and sex!='' ">
AND sex = #{sex}
</if>
<if test="email!=null and email!='' ">
AND email = #{email}
</if>
</trim>
</select>
<!--根据条件查询员工信息-->
<select id="queryEmpByCondition" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<!--
prefix:前缀
prefixOverrides:前缀重写
suffix:后缀
suffixOverrides:后缀重写
-->
<trim prefix="where" suffixOverrides="AND|OR">
<if test="empName!=null and empName!='' ">
emp_name = #{empName} AND
</if>
<if test="age!=null and age!='' ">
age = #{age} AND
</if>
<if test="sex!=null and sex!='' ">
sex = #{sex} AND
</if>
<if test="email!=null and email!='' ">
<!--
当这个条件满足时,suffixOverrides会将条件后多余的AND会被去掉,
最后一个条件后面的AND也可手动不写
-->
email = #{email} AND
</if>
</trim>
</select>
choose、when、otherwise
相当于if…else if…else
choose是父标签,when和otherwise是写到父标签中的。
when相当于if else if。
与if标签的区别:if是每个条件都会判断。
when相当于 if else if,相当于只走一个条件
otherwise相当于else
when最少要有一个
otherwise最多只能有一个
接口
/**
* 测试choose标签
* 根据emp查询emp列表
*
* @param emp
* @return
*/
List<Emp> queryEmpByChoose(Emp emp);
测试代码
/**
* 测试choose
*
* @throws Exception
*/
@Test
public void testQueryEmpByChoose() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Emp emp = new Emp(null, "zhangsan", 20, "男", "1234@12.com", null);
List<Emp> emps = mapper.queryEmpByChoose(emp);
for (Emp emp1 : emps) {
System.out.println(emp1);
}
}
映射配置文件
<!--根据emp条件查询emp列表-->
<select id="queryEmpByChoose" resultMap="empResultMap">
SELECT
*
FROM
t_emp
<where>
<choose>
<when test="empName!=null and empName!='' ">
emp_name=#{empName}
</when>
<when test="age!=null and age!='' ">
age=#{age}
</when>
<when test="sex!=null and sex!='' ">
sex=#{sex}
</when>
<when test="email!=null and email!='' ">
email=#{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>
注意:
when相当于java中的,if(){}else if(){} else if(){}
otherwise相当于最后的else
foreach标签
批量操作
接口
/**
* 测试根据eid数组进行批量删除
*
* @param eids
* @return
*/
int delEmpByeids(@Param("eids") Integer[] eids);
测试代码
/**
* 测试根据eid数组进行批量删除
*
* @throws Exception
*/
@Test
public void testDelEmpByeids() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Integer[] eids = {9, 10, 11};
int res = mapper.delEmpByeids(eids);
Assert.assertEquals(res, eids.length);
}
映射配置文件
方式一:
<!--测试根据eid数组进行批量删除-->
<delete id="delEmpByeids">
DELETE FROM t_emp
WHERE
<!--
collection:要遍历的集合或数组
open:以什么开始
item:遍历出来的每个元素
separator:遍历出的每个元素之间的分隔符
close:以什么结束
-->
<foreach collection="eids" open="eid IN(" item="eid" separator="," close=")">
#{eid}
</foreach>
</delete>
方式二:
<!--测试根据eid数组进行批量删除-->
<delete id="delEmpByeids">
DELETE FROM t_emp
WHERE
<foreach collection="eids" item="eid" separator="OR">
eid= #{eid}
</foreach>
</delete>
批量添加
接口
/**
* 测试批量添加
*
* @param emps
* @return
*/
int insertEmpList(@Param("emps") List<Emp> emps);
测试代码
/**
* 测试集合批量添加
*
* @throws Exception
*/
@Test
public void testInsertEmpList() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> emps = new ArrayList<Emp>();
Emp emp1 = new Emp(null, "a", 22, "男", "112@12.com", null);
Emp emp2 = new Emp(null, "b", 22, "男", "112@12.com", null);
Emp emp3 = new Emp(null, "c", 22, "男", "112@12.com", null);
Emp emp4 = new Emp(null, "d", 22, "男", "112@12.com", null);
emps.add(emp1);
emps.add(emp2);
emps.add(emp3);
emps.add(emp4);
int res = mapper.insertEmpList(emps);
Assert.assertEquals(res, emps.size());
}
映射配置文件
<!--测试批量添加-->
<insert id="insertEmpList">
INSERT INTO t_emp VALUES
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
<!--批量添加emp-->
<insert id="insertEmps">
INSERT INTO t_emp
<trim prefix="(" suffixOverrides="," suffix=")">
emp_name,
age,
sex,
email
</trim>
VALUES
<foreach collection="empList" item="emp" separator=",">
<trim prefix="(" suffixOverrides="," suffix=")">
#{emp.empName},
#{emp.age},
#{emp.sex},
#{emp.email},
</if>
</trim>
</foreach>
</insert>
如果传入的值有null的话,SQL会报错,个数不匹配:
<!--测试批量添加-->
<insert id="insertEmpList">
INSERT INTO t_emp
<trim prefix="(" suffix=")">
emp_name,
age,
sex,
email
</trim>
VALUES
<!--separator是用来分割遍历出来的每个emp元素的:(emp1),(emp2),(emp3)-->
<foreach collection="emps" item="emp" separator=",">
<!--
每个元素内部字段之间分隔符需要手动添加
suffixOverrides:后缀重写,当条件最后多个分割符的时候,可以被去掉
-->
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="emp.empName!=null and emp.empName!=''">
#{emp.empName},
</if>
<if test="emp.age!=null and emp.age!=''">
#{emp.age},
</if>
<if test="emp.sex!=null and emp.sex!=''">
#{emp.sex},
</if>
<if test="emp.email!=null and emp.email!=''">
#{emp.email},
</if>
</trim>
</foreach>
</insert>
sql标签
将常用的sql,抽取出来到一个sql片段中
举例:
<!--查询emp列表-->
<select id="queryEmpList" resultMap="empResultMap">
SELECT
*
FROM
t_emp
</select>
<select id="queryEmpList" resultMap="empResultMap">
SELECT
eid,
emp_name,
age,
sex,
email
FROM
t_emp
</select>
使用sql片段
<sql id="empColumn">
eid,
emp_name,
age,
sex,
email
</sql>
<!--查询emp列表-->
<select id="queryEmpList" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
</select>
抽取出来的sql片段,其他sql可复用
动态SQL的CRUD组合拳
添加
接口
/**
* 添加emp
*
* @param emp
* @return
*/
int insertEmp(Emp emp);
映射配置文件
<!--添加emp-->
<insert id="insertEmp">
INSERT INTO t_emp
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="empName!=null and empName!=''">
emp_name,
</if>
<if test="age!=null and age!=''">
age,
</if>
<if test="sex!=null and sex!=''">
sex,
</if>
<if test="email!=null and email!=''">
email,
</if>
</trim>
VALUES
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="empName!=null and empName!=''">
#{empName},
</if>
<if test="age!=null and age!=''">
#{age},
</if>
<if test="sex!=null and sex!=''">
#{sex},
</if>
<if test="email!=null and email!=''">
#{email},
</if>
</trim>
</insert>
添加并赋值主键
并将自增后的主键值赋值给eid属性
接口
/**
* 添加emp
* 并返回自增的主键eid
*
* @param emp
* @return
*/
int insertEmpReturnEid(Emp emp);
映射配置文件
<!--添加emp,并将自增后的主键赋值给eid属性-->
<insert id="insertEmpReturnEid" useGeneratedKeys="true" keyProperty="eid">
INSERT INTO t_emp
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="empName!=null and empName!=''">
emp_name,
</if>
<if test="age!=null and age!=''">
age,
</if>
<if test="sex!=null and sex!=''">
sex,
</if>
<if test="email!=null and email!=''">
email,
</if>
</trim>
VALUES
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="empName!=null and empName!=''">
#{empName},
</if>
<if test="age!=null and age!=''">
#{age},
</if>
<if test="sex!=null and sex!=''">
#{sex},
</if>
<if test="email!=null and email!=''">
#{email},
</if>
</trim>
</insert>
批量添加
接口
/**
* 批量添加emp
*
* @param empList
* @return
*/
int insertEmps(@Param("empList") List<Emp> empList);
映射配置文件
<!--批量添加emp-->
<insert id="insertEmps">
INSERT INTO t_emp
<trim prefix="(" suffixOverrides="," suffix=")">
emp_name,
age,
sex,
email
</trim>
VALUES
<foreach collection="empList" item="emp" separator=",">
<trim prefix="(" suffixOverrides="," suffix=")">
#{emp.empName},
#{emp.age},
#{emp.sex},
#{emp.email},
</if>
</trim>
</foreach>
</insert>
如果值有null的话,下面的SQL会报错,参数个数不匹配:
<!--批量添加emp-->
<insert id="insertEmps">
INSERT INTO t_emp
<trim prefix="(" suffixOverrides="," suffix=")">
emp_name,
age,
sex,
email
</trim>
VALUES
<foreach collection="empList" item="emp" separator=",">
<trim prefix="(" suffixOverrides="," suffix=")">
<if test="emp.empName!=null and emp.empName!=''">
#{emp.empName},
</if>
<if test="emp.age!=null and emp.age!=''">
#{emp.age},
</if>
<if test="emp.sex!=null and emp.sex!=''">
#{emp.sex},
</if>
<if test="emp.email!=null and emp.email!=''">
#{emp.email},
</if>
</trim>
</foreach>
</insert>
当批量添加的数据为空时,会报错,而不是占用空行
删除
接口
/**
* 根据eid删除emp
*
* @param eid
* @return
*/
int delEmpByEid(@Param("eid") Integer eid);
映射配置文件
<!--根据eid删除emp-->
<delete id="delEmpByEid">
DELETE FROM
t_emp
WHERE
eid = #{eid}
</delete>
批量删除
接口
/**
* 根据eid列表批量删除
*
* @param eids
* @return
*/
int delEmpsByEids(@Param("eids") List<Integer> eids);
映射配置文件
where动态标签如果失效的话,会删表!!!! 别用这个写法
<!--根据eid列表批量删除-->
<delete id="delEmpsByEids">
DELETE FROM t_emp
<!--where动态标签可能会导致删表-->
<where>
<foreach collection="eids" item="eid" separator="OR">
eid = #{eid}
</foreach>
</where>
</delete>
方式二:
集合里没数据的时候,多个WHERE关键字,会报错。宁愿报错也不能删表!!!
<!--根据eid列表批量删除-->
<delete id="delEmpsByEids">
DELETE FROM t_emp
WHERE
<foreach collection="eids" item="eid" separator="OR">
eid = #{eid}
</foreach>
</delete>
方式三:
<delete id="delEmpsByEids2">
DELETE FROM t_emp
WHERE
<foreach collection="eids" open="eid IN(" item="eid" separator="," close=")">
#{eid}
</foreach>
</delete>
方式四:
<delete id="delEmpsByEids3">
DELETE FROM t_emp
WHERE eid IN
<foreach collection="eids" open="(" item="eid" separator="," close=")">
#{eid}
</foreach>
</delete>
更新
接口
/**
* 更新emp
*
* @param emp
* @return
*/
int updateEmp(Emp emp);
映射配置文件
<!--更新emp-->
<update id="updateEmp">
UPDATE t_emp
<set>
<if test="empName!=null and empName!=''">
emp_name = #{empName},
</if>
<if test="age!=null and age!=''">
age = #{age},
</if>
<if test="sex!=null and sex!=''">
sex = #{sex},
</if>
<if test="email!=null and email!=''">
email = #{email},
</if>
</set>
WHERE eid = #{eid}
</update>
查询
接口
/**
* 根据eid查询emp
*
* @param eid
* @return
*/
Emp queryEmpByEid(@Param("eid") Integer eid);
映射配置文件
<!--根据eid查询emp-->
<select id="queryEmpByEid" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
WHERE
eid = #{eid}
</select>
查询列表
接口
/**
* 查询emp列表
*
* @return
*/
List<Emp> queryEmpList();
映射配置文件
<!--查询emp列表-->
<select id="queryEmpList" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
</select>
缓存
缓存只针对查询功能有效。
一级缓存(默认开启)
一级缓存是SqlSession级别的,同一个sqlsession查询的数据会被缓存,是不区分Mapper 的,下次查询相同的数据,会从缓存中直接取,而不走数据库查询了。
接口
/**
* 根据eid获取emp
*
* @param eid
* @return
*/
Emp queryEmp(@Param("eid") Integer eid);
映射配置文件
<resultMap id="empResultMap" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<sql id="empColumn">
eid,
emp_name,
age,
sex,
email
</sql>
<!--根据eid获取emp-->
<select id="queryEmp" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
WHERE
eid = #{eid}
</select>
测试代码1
/**
* 测试同一个Sqlsession中查询两次同样的数据
*
* @throws Exception
*/
@Test
public void testQueryEmp1() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper.queryEmp(1);
System.out.println(emp1);
System.out.println("=======================");
Emp emp2 = mapper.queryEmp(1);
System.out.println(emp2);
}
执行情况
同一个Sqlsession的情况下,执行两次相同的查询,只执行了一次SQL。
说明,只查了一次数据库。另一次是从缓存中获取得到的。
测试代码2
/**
* 测试同一个sqlsession情况下,不同mapper执行情况
* @throws Exception
*/
@Test
public void testQueryEmp2() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper mapper1 = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper1.queryEmp(1);
System.out.println(emp1);
System.out.println("=======================");
CacheMapper mapper2 = sqlSession.getMapper(CacheMapper.class);
Emp emp2 = mapper2.queryEmp(1);
System.out.println(emp2);
}
执行情况
测试代码3
/**
* 测试不同sqlsession
*
* @throws Exception
*/
@Test
public void testQueryEmp3() throws Exception {
SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.queryEmp(1);
System.out.println(emp1);
System.out.println("=======================");
SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp2 = mapper2.queryEmp(1);
System.out.println(emp2);
}
执行情况
缓存失效情况
- 不同的sqlsession对应不同的一级缓存;
- 同一个sqlsession但是查询条件不同;
- 同一个sqlsession两次查询期间执行了任何一次增删改的操作;
- 同一个sqlsession两次查询期间手动清空了缓存;
缓存只是用来提高我们的查询速度的,并不能影响查询的真实性。
执行任意一次增删改之后,都会将一级缓存中的数据清空。
手动清空缓存:sqlSession.clearCache();
测试代码(测试清空缓存)
/**
* 测试手动清空缓存后是否还从缓存中取数据
*
* @throws Exception
*/
@Test
public void testQueryEmp5() throws Exception {
SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.queryEmp(1);
System.out.println(emp1);
System.out.println("=======================");
//手动清空缓存
sqlSession1.clearCache();
System.out.println("=======================");
Emp emp2 = mapper1.queryEmp(1);
System.out.println(emp2);
}
执行结果
二级缓存
二级缓存是sqlsessionFactory级别,且需要手动开启。
通过同一个sqlsessionFactory创建的sqlsession查询的结果会被缓存。此后,若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启条件:
- 在核心配置文件中,设置全局配置属性:cacheEnabled=“true” ,默认为true,不需设置;
- 在映射配置文件中设置标签
- 二级缓存必须在sqlsession关闭或提交之后才有效;(当sqlsession没有提交或关闭时,会保存到一级缓存)
- 查询的数据所转换的实体类型必须实现序列化接口;
接口
/**
* 根据eid查询emp
*
* @param eid
* @return
*/
Emp queryEmp(@Param("eid") Integer eid);
实体类(实现序列化接口)
package com.test.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* 员工表实体
*
* @author zhangzengxiu
* @date 2022/9/11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Emp implements Serializable {
private static final long serialVersionUID = 1490329483937671473L;
/**
* 主键
*/
private Integer eid;
/**
* 员工姓名
*/
private String empName;
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
/**
* 邮箱
*/
private String email;
/**
* 部门
*/
private Dept dept;
}
测试代码
/**
* 测试二级缓存
*/
@Test
public void testQueryEmp() {
//加载核心配置文件
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession 设置为自动提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper2 mapper1 = sqlSession1.getMapper(CacheMapper2.class);
Emp emp1 = mapper1.queryEmp(1);
System.out.println(emp1);
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper2 mapper2 = sqlSession2.getMapper(CacheMapper2.class);
Emp emp2 = mapper2.queryEmp(1);
System.out.println(emp2);
} catch (IOException e) {
e.printStackTrace();
}
}
映射配置文件(手动开启二级缓存)
<resultMap id="empResultMap" type="com.test.pojo.Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<sql id="empColumn">
eid,
emp_name,
age,
sex,
email
</sql>
<!--开启二级缓存-->
<cache/>
<!--根据eid获取emp-->
<select id="queryEmp" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
WHERE
eid = #{eid}
</select>
执行结果
缓存并未生效,原因是:未关闭sqlsession。
测试代码(关闭sqlsession)
/**
* 测试二级缓存
*/
@Test
public void testQueryEmp() {
//加载核心配置文件
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession 设置为自动提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper2 mapper1 = sqlSession1.getMapper(CacheMapper2.class);
Emp emp1 = mapper1.queryEmp(1);
System.out.println(emp1);
//关闭sqlsession1
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper2 mapper2 = sqlSession2.getMapper(CacheMapper2.class);
Emp emp2 = mapper2.queryEmp(1);
System.out.println(emp2);
//关闭sqlsession2
sqlSession2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
执行结果
SQL只执行了一次,说明缓存生效。
增删改操作既会失效一级缓存也会失效二级缓存;手动清空缓存只针对一级缓存有效。
缓存失效情况
两次查询之间执行了任意增删改,会使一级和二级缓存同时失效;
二级缓存相关配置
mapper配置文件中,添加的cache标签可以设置属性:
-
eviction:缓存回收策略(缓存是缓存到内存中的)
LRU:最近最少使用,移除最长时间不被使用的对象。(默认使用)
FIFO:先进先出,按照缓存顺序进行移除
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象
-
flushInterval:刷新间隔,单位:毫秒
MyBatis缓存默认多长事件刷新一次,默认情况下不设置,缓存仅仅调用语句时刷新(增删改语句)
-
readOnly:只读,true/false
true:只读缓存,会给所有调用者返回缓存对象的相同实例,就是把缓存实例直接返给我们的用户,不能直接操作这个缓存,会导致与数据库数据不一致。因此,这些对象不能被修改,提供了很重要的性能优势。
false:读写缓存,会对返回缓存对象的拷贝(通过序列化),操作拷贝对象不会影响原对象,速度较慢但是安全。默认是false
-
size:引用数目,正整数。
当前缓存最多能存储多少个对象,设置过大容易导致内存溢出;
-
type:可以使用第三方缓存
MyBatis缓存查询顺序
- 先查询大范围的二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以直接使用;
- 二级缓存未命中的话,再去查询一级缓存;
- 一级缓存也未命中,则查询数据库;
- sqlsession未关闭前,数据是保存到一级缓存中的。当sqlsession提交后,一级缓存的数据会写入到二级缓存
整合第三方缓存EHCache
MyBatis作为一个持久层框架,MyBatis缓存可提高查询效率,MySQL是将数据存储到磁盘上,查询MySQL上的数据会设计到IO操作,查询缓存的时候自然比磁盘文件快。MyBatis可以使用第三方技术来代替二级缓存,一级缓存不能被第三方技术代替。
pom依赖
<!--MyBatis EHCache整合包-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!--slf4j日志门面具体实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
//TODO
逆向工程
//TODO
分页插件
pom依赖
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
配置分页插件(mybatis-config.xml)
<plugins>
<!--分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
接口
/**
* 查询emp列表
*
* @return
*/
List<Emp> queryEmpList();
映射配置文件
<!--查询emp列表-->
<select id="queryEmpList" resultMap="empResultMap">
SELECT
<include refid="empColumn"/>
FROM
t_emp
</select>
测试代码
/**
* 测试查询emp列表
* 分页
*
* @throws Exception
*/
@Test
public void testQueryEmpListWithPage() throws Exception {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
PageMapper mapper = sqlSession.getMapper(PageMapper.class);
//开启分页
PageHelper.startPage(2, 2);
List<Emp> emps = mapper.queryEmpList();
for (Emp emp : emps) {
System.out.println(emp);
}
}
执行结果
获取返回的Page
{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
获取更多参数
//开启分页
PageHelper.startPage(2, 2);
List<Emp> emps = mapper.queryEmpList();
//导航页码
int navigatePages = 3;
PageInfo<Emp> pageInfo = new PageInfo<Emp>(emps, navigatePages);
结果
PageInfo
{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false},
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
常用数据:
pageNum:当前页的页码
pageSize:每页显示条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页姨妈
nextPage:下一页页码
isFirstPage/isLastPage:是否为第/最后一页
hasPreviousPage/ hasNextPage:是否存在上/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码 [1, 2, 3]