MyBatis for Beginners 1
This tutorial is for newbies who want to learn MyBatis in a quite easy way.
- MyBatis
- 为什么要使用 MyBatis – 现有持久化技术的对比
- 开发环境的准备
- idea 中模板设置
- CRUD
- SQL 特殊执行
- 模糊查询
- 配置解析
- 类型别名 typeAliases
- 映射器
- 生命周期和作用域
- ResultMap 结果集映射
- 日志
- 分页
- 注解开发
- 执行流程剖析
- 环境搭建
- Could not find resource dao/*.xml
- 多对一处理
- 一对多处理
- 动态 SQL
- MyBatis 缓存
- 逆向工程
- 分页插件
MyBatis
为什么要使用 MyBatis – 现有持久化技术的对比
-
JDBC
-
SQL 夹在 Java 代码块里,耦合度高导致硬编码内伤
-
维护不易且实际开发需求中 sql 有变化,频繁修改的情况多见
-
Hibernate 和 JPA
-
长难复杂 SQL,对于 Hibernate 而言处理也不容易
-
内部自动生产的 SQL,不容易做特殊优化
-
基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。导致数据库性能下降
-
MyBatis
-
对开发人员而言,核心 sql 还是需要自己优化
-
sql 和 java 编码分开,功能边界清晰,一个专注业务、一个专注数据
开发环境的准备
思路:搭建环境——》导入 Mybatis——》编写代码——》测试
1 mysql 中创建表
存储引擎是innodb。innoDB 是 MySQL 上第一个提供外键约束的数据存储引擎,除了提供事务处理外,InnoDB 还支持行锁,提供和 Oracle 一样的一致性的不加锁读取,能增加并发读的用户数量并提高性能,不会增加锁的数量。InnoDB 的设计目标是处理大容量数据时最大化性能,它的 CPU 利用率是其他所有基于磁盘的关系数据库引擎中最有效率的。
InnoDB 是一套放在 MySQL 后台的完整数据库系统,InnoDB 有它自己的缓冲池,能缓冲数据和索引,InnoDB 还把数据和索引存放在表空间里面,可能包含好几个文件,这和 MyISAM 表完全不同,在 MyISAM 中,表被存放在单独的文件中,InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB
What is engine InnoDB?
InnoDB is a general-purpose storage engine that balances high reliability and high performance. In MySQL 5.6, InnoDB is the default MySQL storage engine. Unless you have configured a different default storage engine, issuing a CREATE TABLE statement without an ENGINE clause creates an InnoDB table.
create database `mybatis`;
create table `user`(
`id` int(20) not null primary key,
`name` varchar(30) default null,
`pwd`varchar(30) default null
)
插入三个属性
Insert into `user` (`id`, `name`, `pwd`) values
(1, 'aa', '1234556'),
(2, 'bb', '123e56'),
(3, 'cc', '1234gg6')
2 maven 工程, 把 src 文件夹删掉,变成父工程, 导入依赖
导入 MyBatis 框架的 jar 包、Mysql 驱动包、log4j 的 jar 包
- myBatis-3.4.1.jar
- mysql-connector-java-5.1.37-bin.jar
- log4j.jar
- junit
3 maven 子工程
4. 创建 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 核心配置文件-->
<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://192.168.56.10:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
transactionManager type=“JDBC”/
type:设置事务管理方式,type=“JDBC|MANAGED”
type=“JDBC”:设置当前环境的事务管理都必须手动处理
type=“MANAGED”:设置事务被管理,例如 spring 中的 AOP
mysql数据库用的是gbk编码,而项目数据库用的是utf-8编码。这时候如果添加了useUnicode=true&characterEncoding=UTF-8 ,那么作用有如下两个方面:
1. 存数据时:
数据库在存放项目数据的时候会先用UTF-8格式将数据解码成字节码,然后再将解码后的字节码重新使用GBK编码存放到数据库中。
2.取数据时:
在从数据库中取数据的时候,数据库会先将数据库中的数据按GBK格式解码成字节码,然后再将解码后的字节码重新按UTF-8格式编码数据,最后再将数据返回给客户端。
5. 编写 mybatis 工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 官网 获取sqlsession对象
String resource = "mybatis-config.xml";
InputStream inputStream = inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
6. 编写代码
-
JDBC 时期
-
实体类 (pojo -》 user 实体类)
-
Dao 接口 (UserDao)
-
接口实现类 (UserDaoImpl)
-
mybatis
-
实体类 (pojo -》 user 实体类)
-
Dao 接口 (UserDao)
-
XxxMapper.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">
<!--namespace=绑定一个对应的Dao/ Mapper接口-->
<mapper namespace="dao.UserDao">
<!-- select 查询语句-->
<select id="getUserList" resultType="pojo.User">
select * from mybatis.user
</select>
</mapper>
7. 测试
public class UserDaoTest {
@Test
public void test() {
// step1: 获取 sqlsession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// step2: 执行
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
org.apache.ibatis.binding.BindingException: Type interface dao.UserDao is not known to the MapperRegistry
每一个 Mapper.xml 都需要在 Mybatis 核心配置文件中注册
could not find Mapper.xml 配置文件无法导出生效的时候在 pom 文件中 build 里面配置
idea 中模板设置
配置文件
以后直接右键会出现 new mybatis.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 namespace="">
</mapper>
MyBatis 获取参数值的两种方式
MyBatis 获取参数值的两种方式:${}和#{}
${}的本质就是字符串拼接,#{}的本质就是占位符赋值
${}使用字符串拼接的方式拼接 sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接 sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
select * from t_user where username = #{username}
select * from t_user where username = ‘${username}’
1. 单个字面量类型的参数
若 mapper 接口中的方法参数为单个的字面量类型 此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号
2. 多个字面量类型的参数
此时 MyBatis 会自动将这些参数放在一个 map 集合中,以 arg0,arg1…为键,以参数为值;
以 param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问 map 集合的键就可以获取相对应的
值,注意${}需要手动加单引号
User checkLogin(String username, String password);
错误的方法
select * from t_user where username = #{username} and password = #{password}
正确的, 可以把 arg0 替换为 param1
3. map 集合类型的参数
第二种是 mybatis 设置键
若 mapper 接口中的方法需要的参数为多个时,此时可以手动创建 map 集合,将这些数据放在 map 中只需要通过${}和#{}访问 map 集合的键就可以获取相对应的值,注意${}需要手动加单引号
SqlSession sqlSession = MybatisUtils.getSqlSession();
// step2: 执行
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("password", "admin");
Student stu = mapper.checkLoginByMap();
System.out.println(allStudent);
sqlSession.close();
4. 实体类类型的参数
若 mapper 接口中的方法参数为实体类对象时 (从游览器中得到实体信息) 此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
int insertUser(User user);
5. 使用@Param 标识参数
可以通过@Param 注解标识 mapper 接口中的方法参数. 此时,会将这些参数放在 map 集合中,以@Param 注解的 value 属性值为键,以参数为值;以 param1,param2…为键,以参数为值;只需要通过${}和#{}访问 map 集合的键就可以获取相对应的值, 注意${}需要手动加单引号
mybatis 会把 @Param 里面的键放进 map,相当于第二第三种情况
public interface TeacherMapper {
Teacher getTeacher(@Param("tid") int var1);
}
建议就分两种情况,一种就是获取实体类的时候, 另一个种全部加上 @Param
CRUD
查询一个实体类对象
// 根据用户id查询用户信息
Student getStudentById(@Param("id") int id);
<select id="getStudentById" resultType="Student">
select * from Student where id = #{id};
</select>
查询一个 list 集合
// 查询所有的学生信息
List<Student> getAllStudent();
<select id="getAllStudent" resultType="Student">
select * from student;
</select>
查询单个数据
// 根据用户信息总记录是
int getCount();
<select id="getCount" resultType="Integer">
select count(*) from student;
</select>
查询一条数据为 map 集合
Map<String, Object> getUserToMap(@Param(“id”) int id);
select * from t_user where id = #{id}
查询多条数据为 map 集合
method 1
// * 查询所有用户信息为map集合
// * @return
// * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此
// 时可以将这些map放在一个list集合中获取
// */
List<Map<String, Object>> getAllUserToMap();
// <!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
method 2
@MapKey("id")
Map<String, Object> getAllUserToMap();
SQL 特殊执行
模糊查询
// 测试模糊查询
List<Student> getStudentByLike(@Param("username") String username);
不可以
select * from student where
name
like ‘%#{username}%’
method 1
<select id="getStudentByLike" resultType="Student">
select * from student where `name` like '%${username}%'
</select>
method 2
select * from student where
name
like concat('%', #{username}, ‘%')
method 3
select * from student where
name
like “%"#{username}"%”
批量删除
// 批量删除
int deleteMore(@Param("ids") String ids);
不能用 #{}, 会自动加单引号
<select id="deleteMore">
delete from student where id in (${ids})
</select>
mapper.deleteMore(“1, 2, 3”);
动态设置表名
// 动态设置表名,查询所有的用户信息
List<User> getAllUser(@Param("tableName") String tableName);
这里也只能用 ${}, 会出现错误 preparing : select * from ? (BaseJdbcLogger.java)
<select id="getAllUsert" resultType="User">
select * from ${tableName};
</select>
添加功能获取自增的主键
-
t_clazz(clazz_id,clazz_name)
-
t_student(student_id,student_name,clazz_id)
-
添加班级信息
-
获取新添加的班级的 id
-
为班级分配学生,即将某学的班级 id 修改为新添加的班级的 id
// 添加用户信息
int insertUser(User user);
useGeneratedKeys: 设置使用自增的主键
keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数 user 对象的某个属性中
// <!--int insertUser(User user);-->
<select id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</select>
namespace
namespace 中的包名要和 Dao/Mapper 接口的包名一致
注意事项
-
id :就是对应的 namespace 接口中的方法名
-
resuleType :SQL 语句执行的返回值
-
parameterType :参数类型
注意:增删改需要提交事务
编写接口
mapper 的 sql 语句
测试
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用 Map
模糊查询
List
userLike = userMapper.getUserLike("%li%");
配置解析
类型别名 typeAliases
存在别名的是为 Java 类型设置一个短的名字
存在的意义仅用来减少类完全限定的冗余
alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小
更常用: 以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写
<typeAliases>
<typeAlias type="pojo.User" alias="user"/>
</typeAliases>
也可以指定一个包名,Mybatis 会在包名下面搜索 java Bean ,比如: 扫描实体类的包,它的默认别名就是为这个类的类型,首字母小写
<typeAliases>
<package name="pojo"/>
</typeAliases>
在实体类比较少的时候,使用第一种方式
如果实体类十分多,建议使用第二种
第一种可以 DIY 别名,第二种则不行,如果非要改,需要在实体上增加注解
@Alias("user")
public class User implements Serializable {
映射器
MyBatis 中的 mapper 接口相当于以前的 dao。但是区别在于,mapper 仅仅是接口,我们不需要 提供实现类。
MapperRegistry:注册绑定我们的 Mapper 文件;
方式一:
<mappers>
<mapper resource="dao/UserMapper.xml"/>
<!--通配方式-->
<mapper resource="dao/*Mapper.xml"/>
</mappers>
方式二:使用 class 文件绑定注册
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper class="dao.UserMapper"/>
</mappers>
注意点:
接口和他的 mapper 文件都需要在 Mybatis 核心配置文件中注册
接口和他的 mapper 文件配置文件必须再同一包下
方式三:使用扫描包注册
接口和他的 mapper 文件配置文件必须再同一包下
放在 resources 下面的时候,要和 mapper 文件类名字一一对应
比如 java 下面的 com.learn.mybatis.mapper
那 resources 下要把 xml 文件 放进 com.learn.mybatis.mapper 中
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<package name="dao"/>
<mappers>
<package name="dao"/>
</mappers>
注意点:
接口和他的 Mapper 配置文件必须同名! 接口和他的 mapper 文件配置文件必须再同一包下!
1、映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml
例如:表 t_user,映射的实体类为 User,所对应的映射文件为 UserMapper.xml
因此一个映射文件对应一个实体类,对应一张表的操作
MyBatis 映射文件用于编写 SQL,访问以及操作表中的数据
MyBatis 映射文件存放的位置是 src/main/resources/mappers 目录下
2、MyBatis 中可以面向接口操作数据,要保证两个一致:
a>mapper 接口的全类名和映射文件的命名空间(namespace)保持一致
b>mapper 接口中方法的方法名和映射文件中编写 SQL 的标签的 id 属性保持一致
生命周期和作用域
SqlSession:代表 Java 程序和数据库之间的会话。(HttpSession 是 Java 程序和浏览器之间的会话)
SqlSessionFactory:是“生产”SqlSession 的“工厂”。 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的 相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。
SqlSessionFactory
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域。
- 最简单的就是使用单例模式或者静态单例模式
SqlSession(相当于从连接池中获取一个连接)
- 连接到连接池的请求
- 关闭
- SqlSession 的实例不是线程安全的,因此是不能被共享的,它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,
- 一个 SQLSession 可以多次使用它的 getMapper 方法,获取多个 mapper 接口实例
ResultMap 结果集映射
查询的标签 select 必须设置属性 resultType 或 resultMap,用于设置实体类和数据库表的映射 关系
resultType:自动映射,用于属性名和表中字段名一致的情况 resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
resultMap 元素是 MyBatis 中最重要最强大的元素。 ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。 MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。
<resultMap id="UserMap" type="pojo.User">
<id property="id" column="id"/>
<!--column 数据库的字段, property JavaBean中的属性-->
<result property="name" column="name"/>
<result property="password" column="pwd"/>
</resultMap>
resultmap 这里要写完包名,不然会出错
日志
STDOUT_LOGGING
LOG4J
- 导入 log4j 包
- 搜索 log4j.properties 文件配置
参数为当前类的 class
static Logger logger = Logger.getLogger(UserTest.class);
分页
思考:为什么要分页?
减少数据的处理量
语法
select * from users limit startIndex, PageSize;
select * from users limit 0, 2;
mybatis 实现分页, 核心 SQL
-
- 接口
List
getUserByLimit(Map<String, Integer> map);
-
- Mapper.xml
-
- 测试
@Test
public void test3() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<User> limit = mapper.getUserByLimit(map);
for (User user : limit) {
System.out.println(user);
}
}
注解开发
对稍微复杂一点的语句,最好使用 xml 配映射语句
面向接口编程
在真正的开发中,很多时候我们会选择面向 接口编程。
根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的, 对系统设计人员来讲就不那么重要了 ;
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
- 接口从更深层次的理解,应是 定义(规范,约束)与 实现(名实分离的原则)的分离。
- 接口的本身反映了系统设计人员对 系统 的抽象理解。
接口应有两类:
- 第一类是对一个 个体的抽象,它可对应为一个 抽象体(abstract class);
- 第二类是对 一个个体某一方面的抽象,即形成 一个抽象面(interface) ; 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别:
- 面向对象是指,我们考虑问题时,以 对象为单位,考虑它的属性及方法.
- 面向过程是指,我们考虑问题时,以 ***一个具体的流程(事务过程)***为单位,考虑它的实现.
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构
执行流程剖析
1. Resources 获取加载全局配置文件
2. 实例化 sqlsessionfactoryBuilder 对象
3. 解析文件流 XMLConfigBuilder
4. Configuration 所有的配置信息
5. 实例化 sqlsessionfactory 对象
6. transactionCaches
7. 创建 executor 执行器
8. 创建 sqlsession
9. 实现 CRUD (会和 6 连接, 回滚)
10. 查看是否成功 (会和 6 连接, 回滚)
11. 提交事务
12. 关闭
环境搭建
- mybatis01
- src
- main
- java
- com.demo.mybatis
- mapper
- DeptMapper
- pojo
- utils
- mapper
- com.demo.mybatis
- resources
- com/demo/mybatis
- DeptMapper.xml
- jdbc.properties
- log4j.xml
- mybatis-config.xml
- com/demo/mybatis
- test
- java
- com.demo.mybatis
- java
- java
- main
- src
MyBatis 通过字段别名解决字段名和属性名的映射关系
实体类
private String empName;
mysql
emp_name
method 1 别名
<select id="getAllEmp" resultType="Emp">
select * from t_emp
</select>
改为
<select id="getAllEmp" resultType="Emp">
select eid, emp_name empName, age, sex, email from t_emp
</select>
method 2 全局配置 mapUnderscoreToCamelCase
将下划线自动映射为驼峰
<settings>
<!-- 将下划线自动映射为驼峰, emp_name : empName-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
method 3 resultMap
Could not find resource dao/*.xml
- pom 里面
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
- target 里面 classes 下面的 dao 都存在 mapper 和 mapper.xml
但是就是无法读取,所以改为 package 或者 class 才可以
多对一处理
多个学生关联一个老师 : 多对一
老师, 集合, 集合里面很多学生, 一对多
要把表关系设置在多的这一方
Emp 实体类
private Dept dept;
在实体类中是 Dept 实体类,在表格中是 int 类
a. 级联方式处理映射关系
<resultMap id="empAndDeptResultMapOne" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<!-- 级联方式处理映射关系 多对一处理-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>
b. 使用 association 处理映射关系
<resultMap id="empAndDeptResultMapOne" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<!-- association 处理映射关系 多对一处理-->
<!-- javaType 该属性类型-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>
c. 分步查询
<resultMap id="empAndDeptResultMapThree" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept"
select="com.demo.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did">
</association>
</resultMap>
<!-- select 是 deptMapper 上面id 的reference -->
<select id="getEmpAndDeptByStep" resultMap="empAndDeptResultMapThree">
select * from t_emp where eid = #{eid};
</select>
DeptMapper
<mapper namespace="com.demo.mybatis.mapper.DeptMapper">
<!-- Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
select * from t_dept where did = #{did}
</select>
</mapper>
结果是 2 个 sql 语句
DEBUG 03-23 15:44:26,621 ==> Preparing: select * from t_emp where eid = ?;
DEBUG 03-23 15:44:26,730 ====> Preparing: select * from t_dept where did = ?
Emp(eid=3, empName=cc, age=22, sex=女, email=123@qq.com, dept=Dept(did=3, deptName=C))
select: 设置分布查询的 sql 的唯一标识(namespace.SQLId 或 mapper 接口的全类名)
分步查询的优点
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个 属性会按需加载
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的 sql。此时可通过 association 和 collection中的 fetchType 属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加 载)|eager(立即加载)”
mybatis-config.xml
<!-- 设计mybatis全局配置-->
<settings>
<!-- 将下划线自动映射为驼峰, emp_name : empName-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
结果
@Test
public void testGetAllEmpAndDept2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpAndDeptByStep(3);
System.out.println(emp.getEmpName());
System.out.println("++++++++++++++++++++++++++++++");
System.out.println(emp.getDept());
}
DEBUG 03-23 16:00:38,203 ==> Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:143) DEBUG 03-23 16:00:38,257 ==> Parameters: 3(Integer) (BaseJdbcLogger.java:143) DEBUG 03-23 16:00:38,331 <== Total: 1 (BaseJdbcLogger.java:143)
cc
++++++++++++++++++++++++++++++
DEBUG 03-23 16:00:38,332 ==> Preparing: select * from t_dept where did = ? (BaseJdbcLogger.java:143) DEBUG 03-23 16:00:38,333 ==> Parameters: 3(Integer) (BaseJdbcLogger.java:143) DEBUG 03-23 16:00:38,335 <== Total: 1 (BaseJdbcLogger.java:143)
Dept(did=3, deptName=C)
Process finished with exit code 0
没有延迟加载
DEBUG 03-23 16:05:20,189 ==> Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:143) DEBUG 03-23 16:05:20,239 ==> Parameters: 3(Integer) (BaseJdbcLogger.java:143) DEBUG 03-23 16:05:20,278 ====> Preparing: select * from t_dept where did = ? (BaseJdbcLogger.java:143) DEBUG 03-23 16:05:20,279 ====> Parameters: 3(Integer) (BaseJdbcLogger.java:143) DEBUG 03-23 16:05:20,283 <==== Total: 1 (BaseJdbcLogger.java:143) DEBUG 03-23 16:05:20,284 <== Total: 1 (BaseJdbcLogger.java:143)
cc
++++++++++++++++++++++++++++++
Dept(did=3, deptName=C)
Process finished with exit code 0
一对多处理
a. collection
部门实体类
List
emps;
ofType:表示该属性对应的集合中存储数据类型
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<!-- Dept getDeptAndEmp(@Param("did") Integer did);-->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
b. 分步查询
Dept getDeptByStepOne(@Param(“did”) Integer did);
<resultMap id="deptAndEmpResultMapOne" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="com.demo.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
column="did"
>
</collection>
</resultMap>
<select id="getDeptByStepOne" resultMap="deptAndEmpResultMapOne">
select * from t_dept where did = #{did}
</select>
List
getDeptAndEmpByStepTwo(@Param(“did”) Integer did);
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
select * from t_emp where did = #{did};
</select>
分布查询中第二步 resultMap 不需要,直接用 resultType, 但是多对多除外
动态 SQL
if
这个是查询,不是插入
List
getEmpByCondition(Emp emp);
where 1=1 是为了当输入的资料不全时还可以有结果
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != '' and empName != null">
and emp_name = #{empName}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
<if test="email != '' and email != null">
and email = #{email}
</if>
</select>
where
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != '' and empName != null">
emp_name = #{empName}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
<if test="email != '' and email != null">
and email = #{email}
</if>
</where>
</select>
trim
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != '' and empName != null">
emp_name = #{empName} and
</if>
<if test="age != '' and age != null">
age = #{age} or
</if>
<if test="sex != '' and sex != null">
sex = #{sex} and
</if>
<if test="email != '' and email != null">
email = #{email}
</if>
</trim>
</select>
trim 若标签中有内容,否则就没效果
prefix:在 trim 标签中的内容的前面添加某些内容
prefixOverrides:在 trim 标签中的内容的前面去掉某些内容
suffix:在 trim 标签中的内容的后面添加某些内容
suffixOverrides:在 trim 标签中的内容的后面去掉某些内容
注意:where 标签不能去掉条件最后多余的 and
choose、when、otherwise
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != '' and empName != null">
emp_name = #{empName}
</when>
<when test="age != '' and age != null">
age = #{age}
</when>
<when test="sex != '' and sex != null">
sex = #{sex}
</when>
<when test="email != '' and email != null">
email = #{email}
</when>
<otherwise>
did = 1;
</otherwise>
</choose>
</where>
</select>
when 至少有一个
otherwise 至多有一个
foreach
通过数组实现批量删除
int deleteMoreByArray(@Param(“eids”) Integer[] eids);
@Param(“eids”) 要写,不然结果是 Parameter
eids
not found. Available parameters are [array, arg[0]]
第一种
<!-- int deleteMoreByArray(Integer eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
(
<foreach collection="eids" item="eid" separator=",">
#{eid}
</foreach>
)
<!-- ()都可以去掉 -->
<foreach collection="eids" item="eid" separator="," open="(", close=")">
#{eid}
</foreach>
</delete>
通过 list 实现批量插入
int insertMoreByList(@Param(“emps”) List
emps);
<!-- int deleteMoreByArray(Integer eids);-->
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
</foreach>
</insert>
DEBUG <== Updates: 1 (BaseJdbcLogger.java:143)
出现程序执行, 数据库没更新, 因为 mybatis 默认不是自动提交事务的, openSession() 的括号里写 true, 设定自动提交事务,
collection:设置要循环的数组或集合
item:表示集合或数组中的每一个数据
separator:设置循环体之间的分隔符
open:设置 foreach 标签中的内容的开始符
close:设置 foreach 标签中的内容的结束符
SQL 片段
<sql id="empColumns">
eid,ename,age,sex,did
</sql>
select <include refid="empColumns"></include> from t_emp
把常用的属性设置为片段,下面就直接引用了
MyBatis 缓存
一级缓存
一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就 会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况
-
- 不同的 SqlSession 对应不同的一级缓存
-
- 同一个 SqlSession 但是查询条件不同
-
- 同一个 SqlSession 两次查询期间执行了任何一次增删改操作
-
- 同一个 SqlSession 两次查询期间手动清空了缓存
二级缓存
二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件
- a>在核心配置文件中,设置全局配置属性 cacheEnabled=“true”,默认为 true,不需要设置
- b>在映射文件中设置标签**
** - c>二级缓存必须在 SqlSession 关闭或提交之后有效
- d>查询的数据所转换的实体类类型必须实现序列化的接口
二级缓存失效的情况
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
MyBatis 缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession 关闭之后,一级缓存中的数据会写入二级缓存
逆向工程
1. 添加依赖和插件
2. 创建 MyBatis 的核心配置文件
3. 创建逆向工程的配置文件
文件名必须是:generatorConfig.xml
4.执行 MBG 插件的 generate 目标
Plugins -> mybatis-generator
分页插件
1. 依赖
pagehelper
2. 配置分页插件
在 MyBatis 的核心配置文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
3. 分页插件的使用
在查询功能之前使用 PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数
在查询获取 list 集合之后,使用 PageInfo pageInfo = new PageInfo<>(List list, int
navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的页码数
分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
常用数据:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
☕☕☕☕