一、MyBatis 的特性
① MyBatis 是一款优秀的半自动的 ORM (Object Relation Mapping) 持久层框架
② MyBatis 支持自定义 SQL、存储过程以及高级映射。
③ MyBatis 避免了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
④ MyBatis 可以通过简单的 XML 或 注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通的 Java 对象)为数据库中的记录。
二、MyBatis 的优缺点
三、搭建 MyBatis
1. 开发环境
IDE、maven、MySQL、MyBatis
2. 创建 maven 工程
打包方式:jar
引入依赖:
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
3. 创建 MyBatis 的核心配置文件
一般命名为
mybatis-conflg.xml
核心配置文件:
主要用于配置连接数据库的环境
以及 MyBatis 的全局配置信息
存放的位置是 src/main/resources 目录下
<?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>
<!--引入映射文件-->
<mappers>
<package name="mappers/UserMapper.xml"/>
</mappers>
</configuration>
4. 创建 mapper 接口
mybatis 中的 mapper 接口相当于以前的 dao
但是 mapper 只是接口,并不需要提供实现类
public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}
5. 创建 MyBatis 的映射文件
ORM (Object Relationship Mapping) 对象关系映射
对象:Java 的实体类对象
关系:关系型数据库
映射:二者之间的对应关系
Java 概念 | 数据库 概念 |
类 | 表 |
属性 | 字段/列 |
对象 | 记录/行 |
(1) 映射文件命名规则:表所对应的实体类的类名 + Mapper.xml
例如:表 t_user,映射的实体类为 User,所对应的映射文件为 UserMapper.xml
MyBatis 映射文件用于编写 SQL,访问以及操作表中的数据
MyBatis 映射文件存放的位置是 src/main/resources/mappers 目录下
(2) MyBatis 中可以面向接口操作数据,要保证两个一致
a > mapper 接口的全类名和映射文件的命名空间 (namespace) 保持一致
b > 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.zh.mybatis.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
</insert>
</mapper>
6. 通过 junit 测试功能
//读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
//SqlSession sqlSession = sqlSessionFactory.openSession();
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配
映射文件中的SQL标签,并执行标签中的SQL语句
int result = userMapper.insertUser();
//sqlSession.commit();
System.out.println("结果:"+result);
SqlSession:代表Java程序和数据库之间的会话
(HttpSession 是Java程序和浏览器之间的会话)
SqlSessionFactory:是'生产' SqlSession 的 '工厂'
工厂模式:如果创建某一个对象,使用的过程基本固定,
那么我们就可以把创建这个对象的相关代码封装到
一个'工厂类'中,以后都使用这个工厂来生产我们需要的对象
7. 加入 log4j 日志功能
①加入依赖
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
② 加入 log4j 的配置文件
log4j 的配置文件为 log4j.xml,存放的位置是 src/main/resources 的目录下
<?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(调试)
打印内容越来越详细
③ 测试查询功能
UserMapper.java
//根据id查询用户信息
User getUserById();
//查询所有的用户
List<User> getAllUser();
UserMapper.xml
<!--User getUserById();-->
<!--
resultType:设置结果类型,即查询的数据要转换为的java类型
resultMap:自定义映射,处理多对一或一对多的映射关系
-->
<select id="getUserById" resultType="com.zh.mybatis.pojo.User">
select * from t_user where id = 1
<select>
<!--List<User> getAllUser();-->
<select id="getUserById" resultType="com.zh.mybatis.pojo.User">
select * from t_user
<select>
MyBatisTest.java
@Test
public void testGetUserById(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById();
System.out.println(user);
}
@Test
public void testGetAllUser(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.getAllUser();
list.forEach(System.out:println);
}
四、核心配置文件详解
核心配置文件中的标签必须按照固定的顺序:
properties? ,settings?, typeAliases? ,typeHandlers?, objectFactory? ,objectWrapperFactory?, reflectorFactory?,plugins?, environments? ,databaseIdProvider?, mappers?
<?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>
<!--
MyBatis核心配置文件中,标签的顺序:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
plugins?,environments?,databaseIdProvider?,mappers?
-->
<!--引入properties文件-->
<properties resource="jdbc.properties" />
<!--设置类型别名-->
<typeAliases>
<!--
typeAlias:设置某个类型的别名
属性:
type:设置需要设置别名的类型
alias:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,
即类名且不区分大小写
-->
<!--<typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>-->
<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<!--
environments:配置多个连接数据库的环境
属性:
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
environment:配置某个具体的环境
属性:
id:表示连接数据库的环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理方式
属性:
type="JDBC|MANAGED"
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方
式,事务的提交或回滚需要手动处理
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:配置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
POOLED:表示使用数据库连接池缓存数据库连接
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<!--设置连接数据库的驱动-->
<property name="driver" value="${jdbc.driver}"/>
<!--设置连接数据库的连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--设置连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--设置连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/ssmserverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
五、MyBatis 的CRUD
1. 增
<!--int insertUser();--><insert id = "insertUser" >insert into t_user values(null,'admin','123456',23,' 男 ')</insert>
2. 删
<!--int deleteUser();--><delete id = "deleteUser" >delete from t_user where id = 7</delete>
3. 改
<!--int updateUser();--><update id = "updateUser" >update t_user set username='ybc',password='123' where id = 6</update>
4. 查
(1) 查询一个实体类对象
<!--User getUserById();--><select id = "getUserById" resultType = "com.atguigu.mybatis.bean.User" >select * from t_user where id = 2</select>
(2) 查询 list 集合
<!--List<User> getUserList();--><select id = "getUserList" resultType = "com.atguigu.mybatis.bean.User" >select * from t_user</select>
注意:
查询的标签 select 必须设置属性 resultType 或 resultMap,
用于设置实体类和数据库表的映射关系
resultType:自动映射,用于属性名和表中字段名一致的情况
resultMap:自定义映射,用于一对多或多对一或字段名和属
性名不一致的情况
六、MyBatis 获取参数值的两种方式
${} 和 #{}
① ${} 本质就是字符串拼接,#{} 本质就是占位符赋值
② ${} 使用字符串拼接的方式拼接 sql,若为字符串类型
或日期类型的字段进行赋值时,需要手动加单引号
③ 但是 #{} 使用占位符赋值的方式拼接 sql,此时为字符
串类型或日期类型的字段进行赋值时,可以自动添加
单引号
1. 单个字面量类型的参数
若 mapper 接口中的方法参数为单个的字面量类型
此时可以使用 ${} 和 #{} 以任意的名称获取参数的值,
注意 ${} 需要手动加单引号
2. 多个字面量类型的参数
若 mapper 接口中的方法参数为多个时,
此时 MyBatis 会自动将这些参数放在一个 map 集合中,
以 arg1,arg2...为键,以参数为值;
以 param1,param2...为键,以参数为值;
因此只需要通过 ${} 和 #{} 访问 map 集合的键就可以
获取相对应的值,注意 ${} 需要手动加单引号
3. map 集合类型的参数
若 mapper 接口中的方法需要的参数为多个时,此时
可以手动创建 map 集合,将这些数据放在 map 中,
只需要通过 ${} 和 #{} 访问 map 集合的键就可以获取
相对应的值,注意 ${} 需要手动加单引号
4. 实体类类型的参数
若 mapper 接口中的方法参数为实体类对象时,此时
可以使用 ${} 和 #{},通过访问实体类对象中的属性名
获取属性值,注意 ${} 需要手动加单引号
5. 使用@Param 标识参数
可以通过@Param注解标识 mapper 接口中的方法参数
此时,会将这些参数放在 map 集合中,
以@Param 注解的 value 属性值为键,以参数为值;
以 param1,param2...为键,以参数为值;
只需要通过 ${} 和 #{} 访问 map 集合的键就可以获取相
对应的值,注意 ${} 需要手动加单引号
七、 MyBatis 的各种查询功能
1. 查询一个实体类对象
/*** 根据用户 id 查询用户信息* @param id* @return*/User getUserById ( @Param ( "id" ) int id );<!--User getUserById(@Param("id") int id);--><select id = "getUserById" resultType = "User" >select * from t_user where id = #{id}</select>
2. 查询一个 list 集合
/*** 查询所有用户信息* @return*/List < User > getUserList ();<!--List<User> getUserList();--><select id = "getUserList" resultType = "User" >select * from t_user</select>
当查询的数据为多条时,不能使用实体类作为返回值,
否则会抛出异常 TooManyResultsException;但是若
查询的数据只有一条,可以使用实体类或集合作为返
回值
3. 查询单个数据
/*** 查询用户的总记录数* @return* 在 MyBatis 中,对于 Java 中常用的类型都设置了类型别名* 例如: java.lang.Integer-->int|integer* 例如: int-->_int|_integer* 例如: Map-->map,List-->list*/int getCount ();<!--int getCount();--><select id = "getCount" resultType = "_integer" >select count(id) from t_user</select>
4. 查询一条数据为 map 集合
/*** 根据用户 id 查询用户信息为 map 集合* @param id* @return*/Map < String , Object > getUserToMap ( @Param ( "id" ) int id );<!--Map<String, Object> getUserToMap(@Param("id") int id);--><!-- 结果: {password=123456, sex= 男 , id=1, age=23, username=admin}--><select id = "getUserToMap" resultType = "map" >select * from t_user where id = #{id}</select>
5. 查询多条数据为 map 集合
方式一:
/*** 查询所有用户信息为 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>
方式二:
/*** 查询所有用户信息为 map 集合* @return* 将表中的数据以 map 集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并 且最终要以一个map 的方式返回数据,此时需要通过@MapKey 注解设置 map 集合的键,值是每条数据所对应的 map集合*/@MapKey("id")Map < String , Object > getAllUserToMap ();<!--Map<String, Object> getAllUserToMap();--><!--{1={password=123456, sex=男 , id=1, age=23, username=admin},2={password=123456, sex=男 , id=2, age=23, username= 张三 },3={password=123456, sex=男 , id=3, age=23, username= 张三 }}--><select id = "getAllUserToMap" resultType = "map" >select * from t_user</select>
八、特殊 SQL 的执行
1. 模糊查询
/*** 测试模糊查询* @param mohu* @return*/List < User > testMohu ( @Param ( "mohu" ) String mohu );<!--List<User> testMohu(@Param("mohu") String mohu);--><select id = "testMohu" resultType = "User" ><!--select * from t_user where username like '%${mohu}%'--><!--select * from t_user where username like concat('%',#{mohu},'%')-->select * from t_user where username like "%"#{mohu}"%"</select>
2. 批量查询
/*** 批量删除* @param ids* @return*/int deleteMore ( @Param ( "ids" ) String ids );<!--int deleteMore(@Param("ids") String ids);//ids:9,10--><delete id = "deleteMore" ><!-- delete from t_user where id=5 OR id=6; --><!-- delete from t_user where id in (7,8); --><!--delete from t_user where id in (‘9,10’); -->delete from t_user where id in ( $ {ids})</delete>
3. 动态设置表名
/*** 动态设置表名,查询所有的用户信息* @param tableName* @return*/List < User > getAllUser ( @Param ( "tableName" ) String tableName );<!--List<User> getAllUser(@Param("tableName") String tableName);--><select id = "getAllUser" resultType = "User" >select * from $ {tableName}</select>
4. 添加功能获取自增的主键
场景模拟:
t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
① 添加班级信息
② 获取新添加的班级的 id
③ 为班级分配学生,即将某学的班级 id 修改
为新添加的班级的 id
/*** 添加用户信息* @param user* @return* useGeneratedKeys :设置使用自增的主键* keyProperty :因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user 对象的某个属性中*/int insertUser ( User user );<!--int insertUser(User user);--><insert id = "insertUser" useGeneratedKeys = "true" keyProperty = "id" >insert into t_user values(null,#{username},#{password},#{age},#{sex})</insert>
九、自定义映射 resultMap
1. resultMap 处理字段和属性的映射关系
若字段名和实体类中的属性名不一致,则可以通过
resultMap 设置自定义映射
<!--resultMap :设置自定义映射属性:id :表示自定义映射的唯一标识type :查询的数据要映射的实体类的类型子标签:id :设置 主键 的映射关系result :设置 普通字段 的映射关系association :设置 多对一 的映射关系collection :设置 一对多 的映射关系属性:property :设置映射关系中实体类中的 属性名必须是 sql 查询出的某个字段column :设置映射关系中表中的 字段名必须是处理的实体类类型中的属性名--><resultMap id = "userMap" type = "User" ><id property = "id" column = "id" ></id><result property = "userName" column = "user_name" ></result><result property = "password" column = "password" ></result><result property = "age" column = "age" ></result><result property = "sex" column = "sex" ></result></resultMap><!--List<User> testMohu(@Param("mohu") String mohu);--><select id = "testMohu" resultMap = "userMap" ><!--select * from t_user where username like '%${mohu}%'-->select id,user_name,password,age,sex from t_user where user_name likeconcat('%',#{mohu},'%')</select>
若字段名和实体类中的属性名不一致,但是 字段名符合数据库的规则( 使用_ ),实体类中的 属性名符合Java的规则( 使用驼峰 )此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系a>可以通过 为字段起别名 的方式,保证和实体类中的属性名保持一致b>可以在MyBatis的 核心配置文件 中设置一个 全局配置 信息 mapUnderscoreToCamelCase ,可以在查询表中数据时,自动将_类型的字段名转换为驼峰emp_id:empId,emp_name:empName例如:字段名 user_name ,设置了 mapUnderscoreToCamelCase ,此时字段名就会转换为 userName
设置在 MyBatis-config 模板里
<settings>
<!--将下划线映射为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</setting>
2. 多对一映射处理
场景模拟:
查询员工信息以及员工所对应的部门信息
(1) 级联方式处理映射关系
<resultMap id = "empDeptMap" type = "Emp" ><id column = "eid" property = "eid" ></id><result column = "ename" property = "ename" ></result><result column = "age" property = "age" ></result><result column = "sex" property = "sex" ></result><result column = "did" property = "dept.did" ></result><result column = "dname" property = "dept.dname" ></result></resultMap><!--Emp getEmpAndDeptByEid(@Param("eid") int eid);--><select id = "getEmpAndDeptByEid" resultMap = "empDeptMap" >select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =dept.did where emp.eid = #{eid}</select>
(2) 使用 association 处理映射关系
<resultMap id = "empDeptMap" type = "Emp" ><id column = "eid" property = "eid" ></id><result column = "ename" property = "ename" ></result><result column = "age" property = "age" ></result><result column = "sex" property = "sex" ></result><association property = "dept" javaType = "Dept" ><id column = "did" property = "did" ></id><result column = "dname" property = "dname" ></result></association></resultMap><!--Emp getEmpAndDeptByEid(@Param("eid") int eid);--><select id = "getEmpAndDeptByEid" resultMap = "empDeptMap" >select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =dept.did where emp.eid = #{eid}</select>
(3) 分步查询
① 查询员工信息
/*** 通过分步查询查询员工信息* @param eid* @return*/Emp getEmpByStep ( @Param ( "eid" ) int eid );<resultMap id = "empDeptStepMap" type = "Emp" ><id column = "eid" property = "eid" ></id><result column = "ename" property = "ename" ></result><result column = "age" property = "age" ></result><result column = "sex" property = "sex" ></result><!--select :设置 分步查询 ,查询某个属性的值的 sql的标识 ( namespace.sqlId )column :将sql 以及查询结果中的 某个字段设置 为分步查询的 条件--><association property = "dept"select = "com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column = "did" ></association></resultMap><!--Emp getEmpByStep(@Param("eid") int eid);--><select id = "getEmpByStep" resultMap = "empDeptStepMap" >select * from t_emp where eid = #{eid}</select>
② 根据员工所对应的部门 id 查询部门信息
/*** 分步查询的第二步: 根据员工所 对应的did 查询 部门信息* @param did* @return*/Dept getEmpDeptByStep ( @Param ( "did" ) int did );<!--Dept getEmpDeptByStep(@Param("did") int did);--><select id = "getEmpDeptByStep" resultType = "Dept" >select * from t_dept where did = #{did}</select>
3. 一对多映射处理
(1) collection
/*** 根据部门 id 查新部门以及部门中的员工信息* @param did* @return*/Dept getDeptEmpByDid ( @Param ( "did" ) int did );<resultMap id = "deptEmpMap" type = "Dept" ><id property = "did" column = "did" ></id><result property = "dname" column = "dname" ></result><!--ofType :设置 collection 标签所处理的集合属性中 存储数据的类型--><collection property = "emps" ofType = "Emp" ><id property = "eid" column = "eid" ></id><result property = "ename" column = "ename" ></result><result property = "age" column = "age" ></result><result property = "sex" column = "sex" ></result></collection></resultMap><!--Dept getDeptEmpByDid(@Param("did") int did);--><select id = "getDeptEmpByDid" resultMap = "deptEmpMap" >select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did =emp.did where dept.did = #{did}</select>
(2) 分步查询
① 查询部门信息
/*** 分步查询 部门 和部门中的 员工* @param did* @return*/Dept getDeptByStep ( @Param ( "did" ) int did );<resultMap id = "deptEmpStep" type = "Dept" ><id property = "did" column = "did" ></id><result property = "dname" column = "dname" ></result><collection property = "emps" fetchType = "eager"select = "com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column = "did" ></collection></resultMap><!--Dept getDeptByStep(@Param("did") int did);--><select id = "getDeptByStep" resultMap = "deptEmpStep" >select * from t_dept where did = #{did}</select>
② 根据部门 id 查询部门中的所有员工
/*** 根据部门 id 查询员工信息* @param did* @return*/List < Emp > getEmpListByDid ( @Param ( "did" ) int did );<!--List<Emp> getEmpListByDid(@Param("did") int did);--><select id = "getEmpListByDid" resultType = "Emp" >select * from t_emp where did = #{did}</select>
分步查询的优点:可以实现 延迟加载但是必须在核心配置文件中设置全局配置信息:lazyLoadingEnabled : 延迟加载 的 全局开关 。当开启时,所有关联对象都会延迟加载aggressiveLazyLoading :当开启时,任何方 法的调用都会加载该对象的所有属性。否则,每个属性会 按需加载此时就可以实现按需加载,获取的数据是什么,就只会执行相应的 sql。此时可通过 association和 collection 中的 fetchType 属性设置当前的分步查询是否使用延迟加载, fetchType="lazy( 延迟 加载)|eager( 立即 加载)"
十、动态 SQL
MyBatis 框架的动态 SQL 技术是一种根据特定条件
动态拼装 SQL 语句的功能,它存在的意义是为了解
决拼接 SQL 语句字符串时的痛点问题
1. if
if 标签可通过 test 属性的表达式进行判断,
若表达式的结果为 true,则标签中的内容
会执行;反之标签中的内容不会执行
<!--List<Emp> getEmpListByCondition(Emp emp);--><select id = "getEmpListByMoreTJ" resultType = "Emp" >select * from t_emp where 1=1<if test = "ename != '' and ename != null" >and ename = #{ename}</if><if test = "age != '' and age != null" >and age = #{age}</if><if test = "sex != '' and sex != null" >and sex = #{sex}</if></select>
2. where
where 和 if 一般结合使用
a>若 where 标签中的 if 条件都不满足,则 where 标签
没有任何功能,即不会添加 where 关键字
b>若 where 标签中的 if 条件满足,则 where 标签会自动
添加 where 关键字,并将条件最前方多余的 and 去掉
注意:where 标签不能去掉条件最后多余的 and
<select id = "getEmpListByMoreTJ2" resultType = "Emp" >select * from t_emp<where><if test = "ename != '' and ename != null" >ename = #{ename}</if><if test = "age != '' and age != null" >and age = #{age}</if><if test = "sex != '' and sex != null" >and sex = #{sex}</if></where></select>
3. trim
trim 用于去掉或添加标签中的内容常用属性:prefix :在 trim 标签中的内容的 前面添加 某些内容prefixOverrides :在 trim 标签中的内容的前面 去掉 某些内容suffix :在 trim 标签中的内容的 后面添加 某些内容suffixOverrides :在 trim 标签中的内容的后面 去掉 某些内容
<select id = "getEmpListByMoreTJ" resultType = "Emp" >select * from t_emp<trim prefix = "where" suffixOverrides = "and" ><if test = "ename != '' and ename != null" >ename = #{ename} and</if><if test = "age != '' and age != null" >age = #{age} and</if><if test = "sex != '' and sex != null" >sex = #{sex}</if></trim></select>
4. choose、when、otherwise
choose、when、otherwise 相当于 if...else if...else
when 至少设置一个,otherwise 最多设置一个
<!--List<Emp> getEmpListByChoose(Emp emp);--><select id = "getEmpListByChoose" resultType = "Emp" >select <include refid = "empColumns" ></include> from t_emp<where><choose><when test = "ename != '' and ename != null" >ename = #{ename}</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></choose></where></select>
5. foreach
foreach元素的属性主要有
item,index,collection,open,separator,close。
collection 设置要循环的数组或集合
item 用一个字符串表示数组或集合中的每一个数据,相当于别名
index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置
open 表示该语句以什么开始
separator 表示在每次进行迭代之间以什么符号作为分隔符
close 表示以什么结束。
批量添加、批量删除
<!--int insertMoreEmp(List<Emp> emps);--><insert id = "insertMoreEmp" >insert into t_emp values<foreach collection = "emps" item = "emp" separator = "," >(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)</foreach></insert><!--int deleteMoreByArray(int[] eids);--><delete id = "deleteMoreByArray" >delete from t_emp where<foreach collection = "eids" item = "eid" separator = "or" >eid = #{eid}</foreach></delete><!--int deleteMoreByArray(int[] eids);--><delete id = "deleteMoreByArray" >delete from t_emp where eid in<foreach collection = "eids" item = "eid" separator = "," open = "(" close = ")" >#{eid}</foreach></delete>
6. SQL 片段
sql 片段,可以记录一段公共 sql 片段,在使用的
地方通过 include 标签进行引入
<sql id = "empColumns" >eid,ename,age,sex,did</sql>select <include refid = "empColumns" ></include> from t_emp
十一、MyBatis 的缓存
1. MyBatis 的一级缓存
一级缓存是 SqlSession 级别 的,通过 同一个 SqlSession查询的数据会被 缓存 ,下次 查询相同的数据 ,就会从缓存中直接获取,不会从数据库重新访问使一级缓存 失效 的四种情况:1) 不同 的 SqlSession 对应不同的一级缓存2) 同一个 SqlSession 但是 查询条件不同3) 同一个 SqlSession 两次 查询期间执行了 任何一次 增删改 操作4) 同一个 SqlSession 两次查询期间 手动清空了缓存
2.二级缓存
二级缓存是 SqlSessionFactory 级别,通过 同一个SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取二级缓存开启的条件:a> 在核心配置文件中,设置全局配置属性cacheEnabled="true" ,默认为 true ,不需要设置b> 在映射文件中 设置标签 <cache/>c> 二级缓存必须在 SqlSession 关闭或提交之后有效d> 查询的数据所转换的实体类类型必须实现 序列化的接口使二级缓存 失效 的情况:两次查询之间执行了任意的 增删改 ,会使一级和二级缓存同时失效
3. 二级缓存的相关配置
在 mapper 配置文件中添加的 cache 标签可以设置一些属性:① eviction 属性: 缓存回收 策略,默认的是 LRU 。LRU ( Least Recently Used ) – 最近最少使用的:移除最长时间不被使用的对象。FIFO ( First in First out ) – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。② flushInterval 属性:刷新间隔,单位毫秒默认情况是不设置,也就是没有刷新间隔,缓存仅仅 调用语句时刷新③ size 属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出④ readOnly 属性:只读, true/falsetrue : 只读 缓存;会给所有调用者返回缓存对象的相同实例。因此这些 对象不能被修改 。这提供了很重要的 性能优势 。false : 读写 缓存;会返回缓存对象的 拷贝 (通过序列化)这会 慢 一些,但是 安全 ,因此默认是 false。
4. MyBatis 缓存查询的顺序
先查询二级缓存 ,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。如果二级缓存没有命中, 再查询一级 缓存如果一级缓存也没有命中, 则查询数据库SqlSession 关闭 之后, 一级 缓存中的数据会 写入二级缓存
5. 整合第三方缓存 EHCache
① 添加依赖
<!-- 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>
② 各 jar 包功能
jar
包名称
|
作用
|
mybatis-ehcache
|
Mybatis
和
EHCache
的整合包
|
ehcache
|
EHCache
核心包
|
slf4j-api
|
SLF4J
日志门面包
|
logback-classic
|
支持
SLF4J
门面接口的一个具体实现
|
③ 创建 EHCACache 的配置文件 ehcache.xml
<?xml version="1.0" encoding="utf-8" ?><ehcache xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation = "../config/ehcache.xsd" ><!-- 磁盘保存路径 --><diskStore path = "D:\atguigu\ehcache" /><defaultCachemaxElementsInMemory = "1000"maxElementsOnDisk = "10000000"eternal = "false"overflowToDisk = "true"timeToIdleSeconds = "120"timeToLiveSeconds = "120"diskExpiryThreadIntervalSeconds = "120"memoryStoreEvictionPolicy = "LRU" ></defaultCache></ehcache>
④ 设置二级缓存的类型
<cache type = "org.mybatis.caches.ehcache.EhcacheCache" />
⑤ 加入 logback 日志
存在 SLF4J 时,作为简易日志的 log4j 将失效,此时我们需要借助 SLF4J 的具体实现 logback 来打印日志。创建 logback 的配置文件 logback.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug = "true" ><!-- 指定日志输出的位置 --><appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender" ><encoder><!-- 日志输出的格式 --><!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --><pattern> [%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger][%msg]%n </pattern></encoder></appender><!-- 设置全局日志级别。日志级别按顺序分别是: DEBUG 、 INFO 、 WARN 、 ERROR --><!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --><root level = "DEBUG" ><!-- 指定打印日志的appender ,这里通过 “STDOUT” 引用了前面配置的 appender --><appender-ref ref = "STDOUT" /></root><!-- 根据特殊需求指定局部日志级别 --><logger name = "com.atguigu.crowd.mapper" level = "DEBUG" /></configuration>
⑥ EHCache 配置文件说明
属性名
| 是否必须 | 作用 |
maxElementsInMemory
|
是
|
在内存中缓存的
element
的最大数目
|
maxElementsOnDisk
|
是
|
在磁盘上缓存的
element
的最大数目,
若是
0
表示无穷大
|
eternal
|
是
|
设定缓存的
elements
是否永远不过期。
如果为 true,则缓存的数据始终有效,
如果为
false
那么还要根据timeToIdleSeconds
、
timeToLiveSeconds
判断
|
overflowToDisk
|
是
|
设定当内存缓存溢出的时候是否将过期的
element 缓存到磁盘上
|
timeToIdleSeconds
|
否
|
当缓存在
EhCache
中的数据前后两次访问
的时间超过 timeToIdleSeconds
的属性
取值时, 这些数据便会删除,默认值是0,
也就是可闲置时间无穷大
|
timeToLiveSeconds
|
否
|
缓存
element
的有效生命期,默认是
0.,
也就是 element 存活时间无穷大
|
diskSpoolBufferSizeMB
| 否 |
DiskStore(
磁盘缓存
)
的缓存区大小。
默认是 30MB。每个
Cache
都应该有自己
的一个缓冲区
|
iskPersistent
| 否 |
在
VM
重启的时候是否启用磁盘保存
EhCache
中的数据,默认是false
。
|
diskExpiryThreadIntervalSeconds
| 否 |
磁盘缓存的清理线程运行间隔,默认是
120
秒。每个120s
, 相应的线程会进行
一次
EhCache
中数据的清理工作
|
memoryStoreEvictionPolicy
| 否 |
当内存缓存达到最大,有新的
element
加入的时 候, 移除缓存中 element
的策略。
默认是
LRU
(最近最少使用),可选的有LFU
(最不常使用)和 FIFO (先进先出)
|
十二、MyBatis 的逆向工程
正向工程:先创建 java 实体类,由框架负责根据
实体类生成数据库表。Hibernate 是支持
正向工程的
逆向工程:先创建数据库表,由框架负责根据数据库表,
反向生成如下资源 :
Java 实体类、Mapper 接口、Mapper 映射文件
1. 创建逆向工程的步骤
① 添加依赖和插件
pom.xml
<!-- 依赖 MyBatis 核心包 --><dependencies><dependency><groupId> org.mybatis </groupId><artifactId> mybatis </artifactId><version> 3.5.7 </version></dependency><!-- junit测试 --><dependency><groupId> junit </groupId><artifactId> junit </artifactId><version> 4.12 </version><scope> test </scope></dependency><!-- log4j日志 --><dependency><groupId> log4j </groupId><artifactId> log4j </artifactId><version> 1.2.17 </version></dependency><dependency><groupId> mysql </groupId><artifactId> mysql-connector-java </artifactId><version> 8.0.16 </version></dependency></dependencies><!-- 控制 Maven 在构建过程中相关配置 --><build><!-- 构建过程中用到的插件 --><plugins><!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --><plugin><groupId> org.mybatis.generator </groupId><artifactId> mybatis-generator-maven-plugin </artifactId><version> 1.3.0 </version><!-- 插件的依赖 --><dependencies><!-- 逆向工程的核心依赖 --><dependency><groupId> org.mybatis.generator </groupId><artifactId> mybatis-generator-core </artifactId><version> 1.3.2 </version></dependency><!-- MySQL驱动 --><dependency><groupId> mysql </groupId><artifactId> mysql-connector-java </artifactId><version> 8.0.16 </version></dependency></dependencies></plugin></plugins></build>
② 创建 MyBatis 的核心配置文件
③ 创建逆向工程的配置文件
文件名必须是:generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!--targetRuntime : 执行生成的逆向工程的版本MyBatis3Simple : 生成基本的CRUD(清新简洁版)MyBatis3 : 生成 带条件 的CRUD(奢华尊享版)--><context id = "DB2Tables" targetRuntime = "MyBatis3" ><!-- 数据库的连接信息 --><jdbcConnection driverClass = "com.mysql.cj.jdbc.Driver"connectionURL = "jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"userId = "root"password = "123456" ></jdbcConnection><!-- javaBean的生成策略--><javaModelGenerator targetPackage = "com.atguigu.mybatis.pojo"targetProject = ".\src\main\java" ><property name = "enableSubPackages" value = "true" /><property name = "trimStrings" value = "true" /></javaModelGenerator><!-- SQL映射文件的生成策略 --><sqlMapGenerator targetPackage = "com.atguigu.mybatis.mapper"targetProject = ".\src\main\resources" ><property name = "enableSubPackages" value = "true" /></sqlMapGenerator><!-- Mapper接口的生成策略 --><javaClientGenerator type = "XMLMAPPER"targetPackage = "com.atguigu.mybatis.mapper" targetProject = ".\src\main\java" ><property name = "enableSubPackages" value = "true" /></javaClientGenerator><!-- 逆向分析的表 --><!-- tableName设置为*号,可以对应所有 表 ,此时不写 domainObjectName --><!-- domainObjectName属性指定生成出来的 实体类的类名 --><table tableName = "t_emp" domainObjectName = "Emp" /><table tableName = "t_dept" domainObjectName = "Dept" /></context></generatorConfiguration>
④ 执行 MBG 插件的 generate 目标
⑤ 效果
2. QBC 查询
@Testpublic void testMBG (){try {InputStream is = Resources . getResourceAsStream ( "mybatis-config.xml" );SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder (). build ( is );SqlSession sqlSession = sqlSessionFactory . openSession ( true );EmpMapper mapper = sqlSession . getMapper ( EmpMapper . class );//查询所有数据/*List<Emp> list = mapper.selectByExample(null);list.forEach(emp -> System.out.println(emp));*///根据条件查询/*EmpExample example = new EmpExample();example.createCriteria().andEmpNameEqualTo("张三 ").andAgeGreaterThanOrEqualTo(20);example.or().andDidIsNotNull();List<Emp> list = mapper.selectByExample(example);list.forEach(emp -> System.out.println(emp));*/mapper . updateByPrimaryKeySelective ( newEmp ( 1 , "admin" , 22 , null , "456@qq.com" , 3 ));} catch ( IOException e ) {e . printStackTrace ();}}
十三、分页插件
limit index,pageSize
pageSize:每页显示的条数
pageNum:当前页的页码
index:当前页的起始索引,index=(pageNum-1)*pageSize
count:总记录数
totalPage:总页数
totalPage = count / pageSize;
if(count % pageSize != 0){
totalPage += 1;
}
pageSize = 4,pageNum = 1,index = 0 limit 0,4
pageSize = 4,pageNum = 3,index = 8 limit 8,4
pageSize = 4,pageNum = 6,index = 20 limit 8,4
1.分页插件的使用步骤
① 添加依赖
<dependency><groupId> com.github.pagehelper </groupId><artifactId> pagehelper </artifactId><version> 5.2.0 </version></dependency>
② 配置分页插件
在 MyBatis 的核心配置文件中配置插件
<plugins><!--设置分页插件 --><plugin interceptor = "com.github.pagehelper.PageInterceptor" ></plugin></plugins>
2. 分页插件的使用
a> 在 查询功能之前 使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能pageNum :当前页的页码pageSize :每页显示的条数
b> 在 查询获取 list 集合之后 ,使用 PageInfo<T> pageInfo= new PageInfo<>(List<T> list, int navigatePages)获取分页相关数据list :分页之后的数据navigatePages : 导航分页 的 页码数
c> 分页相关数据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]