Mybatis
项目的文件放置
一般java文件都放在src/main/java
。
配置文件都放在src/main/resources
。
特性
- 定制化SQL
即可以自己手写SQL语句。
- 支持存储过程
- 支持高级映射
即POJO实体类和数据库字段的映射方式可以自定义哦。
- 封装了JDBC代码,和结果集的处理过程,再也不用手写JDBC代码和结果集处理啦。
官方仓库&文档
https://github.com/mybatis/mybatis-3
对比其他持久层框架
- JDBC
- 对于已经打包部署的项目,需要重新编辑代码再打包部署,非常麻烦。
- SQL夹杂在JAVA代码中,会导致硬编码内伤。
- 维护不易。
- 代码冗长。
- Hibernate 和 JPA
- 操作非常简便,开发效率非常高。
- 程序中的长难复杂SQL需要绕过框架。
- 内部自动生产SQL,不易做特殊优化。
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。
- 反射操作太多,导致数据库性能下降。
- MyBatis
- 轻量级,性能出色。
- SQL和JAVA编码分开,功能边界清晰。JAVA代码专注业务,SQL语句专注数据。
- 开发效率稍逊,但能接受。
快速开始
Maven
根据你自己项目的类型创建具体Maven项目即可。
<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>
核心配置文件(最少配置)
习惯上命名:mybatis-config.xml
,非强制。
整合Spring
的时候可以省略。这里目的为了能运行即可,详细配置内容看后续。
作用:
- 配置链接数据库的环境
- 配置
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>
<!--
配置链接数据库的环境
default : 选择那个环境有效
-->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.23.128:3306/ssm?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 引入 MyBatis 的映射文件 : 存放SQL语句 和 POJO的映射方式 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>
</mappers>
</configuration>
Mapper 接口
相当于DAO,但是不用创建实现类,MyBatis会创建代理类,并执行映射文件当中的SQL。
起名规则:实体类的名字 + Mapper
package com.atguigu.mybatis.mapper;
public interface UserMapper {
int insertUser();
}
映射文件
Mapper 接口当中的一个抽象方法 对应 映射文件当中的一个SQL语句。
起名规则:POJO名字 + Mapper.xml
放置位置:src/main/resources/UserMapper.xml
这里我们写一条固定的插入SQL,参数如何传递请请看后方比较详细的笔记。
<?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 :对应的mapper接口 -->
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
<!--
id : 对应接口的方法名称.
-->
<select id="insertUser">
INSERT INTO t_user VALUES (NULL, 'admin', '123456', 23, '男', '12345@qq.com');
</select>
</mapper>
测试功能
从开始到创建 SqlSessionFactory
只用创建一次即可,因此可以单独封装即可。
openSession()
获得 SqlSession
默认是不自动提交事务,因此需要自己手动提交。
package com.atguigu.mybatis;
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 org.junit.Test;
import com.atguigu.mybatis.mapper.UserMapper;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisTest {
@Test
public void testInsert() throws IOException {
// 获取核心配置文件的输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取SqlSessionFactoryBuilder 对象 -> 工厂构建器
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 创建 SqlSession 工厂 -> 创建会话
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
// 获取 会话 对象 -> MyBatis 提供的操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得Mapper接口的代理类 -> 操纵Mapper类执行数据库操作
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行SQL操作
Integer rows = userMapper.insertUser();
System.out.println("rows = " + rows);
// 提交事务 -> 事务是默认开启的
sqlSession.commit();
// 关闭资源
sqlSession.close();
}
}
log4j
日志级别
FATAL
(致命)ERROR
(错误)WARN
(警告)INFO
(信息)DEBUG
(调试)
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<?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="%-5d %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>
<!-- 默认配置,级别为debug 且根据name为log.console和 log.file两个appender输出-->
<root>
<level value="debug"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
核心配置文件详解
environments
可以配置多个环境,比如测试环境和开发环境。
使用id
区分,不能重复。
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.23.128:3306/ssm?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.23.128:3306/ssm?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
transactionManager
事务管理器,使用type来设置事务管理方式。
- type:
- JDBC:表示使用JDBC原生事务管理方式,即可以手动的开启关闭事务,手动的提交和回滚。
- MANAGED:被管理的,例如交给Spring管理。
DataSource
设置数据源,使用type 设置数据源的类型。
- type
- POOLED:使用数据库连接池
- UNPOOLED:不使用数据库连接池,链接直接重新创建
- JNDI:表示使用上下文当中的数据源(了解下)
properties
引入
jdbc.properties
resources
下创建 jdbc.properties
文件
jdbc.url=jdbc:mysql://192.168.23.128:3306/ssm?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.user=root
jdbc.password=123456
核心配置文件当中引入
<?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>
<!-- 引入 properties 文件 -->
<properties resource="jdbc.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>
</mappers>
</configuration>
typeAliases
类型别名,设置了之后,在 Mapper
的 resultType
属性中可以使用简单类型别名,能够比较方便。
typeAlias
type
设置需要起别名的类型alias
设置某个类型的别名(如果不写,那么默认就是类名且不区分大小写)
package
指定一个包下面的别名, 且不区分大小写 注意,跟上方的typeAlias不能一起使用
<?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>
<!-- 引入 properties 文件 -->
<properties resource="jdbc.properties"></properties>
<!-- 别名 -->
<typeAliases>
<typeAlias type="com.atguigu.mybatis.pojo.User" alias="user"></typeAlias>
<!-- 也可以指定一个包下面的别名, 且不区分大小写, 跟上方 typeLias 不能同时使用 -->
<package name="com.atguigu.mybatis.pojo"></package>
</typeAliases>
<!-- ... -->
</configuration>
在 Mapper.xml 文件中使用
<select id="getAllUser" resultType="user">
SELECT * FROM t_user;
</select>
settings
核心全局设置, 下面只介绍几个常用的
下划线转驼峰
<settings>
<!-- 下划线 自动映射 驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
延迟加载
如果某个查询不想使用懒加载,则在 association
和 collection
标签当中设置 fetchType
即可。
<settings>
<!-- 延迟加载
LazyLoadingEnabled: true,开启延迟加载
aggressiveLazyLoading: false, 开启按需加载
-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
mappers
分为单个引入和包扫描的方式
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>
<!--
包扫描方式, 两个条件
1. Mapper 接口和 Mapper.xml 必须在一个包下
2. Mapper 接口必须和 Mapper.xml 名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
resouces
下创建对应的目录放mapper.xml
文件
其实是打包完成之后类文件和映射文件都会到同一个目录下。
IDEA 核心配置模板
常用配置模板设置
获取参数
有两种方式
- 占位符方式
${}
- 字符串拼接方式
#{}
,会自动加上‘’
单个字面量类型(基本数据类型)
注意如果选择使用${}
,要加单引号。
public User getUserByUsername(String username);
<!-- 占位符方式 -->
<select id="getUserByUsername" resultType="user">
SELECT * FROM t_user WHERE username = #{username};
</select>
<!-- 字符串拼接方式 -->
<select id="getUserByUsername" resultType="user">
SELECT * FROM t_user WHERE username = '${username}';
</select>
多个字面量类型(基本数据类型)
当 Mapper
接口中有多个参数的时候,MyBatis
会创建Map
,并使用不同的两种方式。
- param
- 下标从1开始
- arg
- 下标从0开始
上述两者均可混合使用。
User checkLogin(String username, String password);
<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{param1} and password = #{param2};
</select>
<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{arg0} and password = #{arg1};
</select>
Map参数
/**
* {username: "xxxx"}
* {password: "xxxx"}
* @param map
* @return
*/
User checkLoginByMap(Map<String, Object> map);
<!-- User checkLoginByMap(Map<String, Object> map); -->
<select id="checkLoginByMap" resultType="user">
SELECT * FROM t_user WHERE username = #{username} and password = #{password};
</select>
实体类类型参数
package com.kinda.mybatis.pojo;
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String gender;
private String email;
public User(Integer id, String username, String password, Integer age, String gender, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.gender = gender;
this.email = email;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", email='" + email + '\'' +
'}';
}
}
必须有 getter
/ setter
。
单个POJO会形成一个Map
,getter/setter函数去掉get/set后,把首字母改为小写即作为Key
, getter
函数返回的值作为 value
。
属性只跟 getter
/setter
有关系。即如果没有对应的属性,但是存在正常getter
的情况,也是可以的。
void insertUser(User user);
<select id="insertUser">
INSERT INTO t_user VALUES (NULL, #{username}, #{password}, #{age}, #{gender}, #{email})
</select>
mapper接口方法参数上进行@Param注解(最常用)
你只能用 {username, param1, password, param2 }
这四个在配置文件当中。
User checkLogin(@Param("username") String username, @Param("password") String password);
<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{username} and password = #{password};
</select>
查询功能详解
单一项返回
设置 resultType 即可,可以使用别名。
当SQL语句返回多条记录的时候,会报:TooManyResultException
的错误。关联提醒
User getUserById(@Param("id") String id)
<select id="getUserById" resultType="user">
SELECT * FROM t_user WHERE id = #{id}
</select>
多项返回
设置 resultType 依然为 POJO 的类型。
⚠️ 注意,若返回结果不止一项,那么一定不能只用实体类去接受,一定要用List。但如果返回只有一项,既可以用实体类接受,也可以用List接受。
List<User> getAllUser();
<select id="getAllUser" resultType="user">
SELECT * FROM t_user;
</select>
单行单列
MyBatis 为 java中常用的类型设置了类型别名。
int → java.lang.Integer
string → java.lang.String
_int → int
map → java.utils.Map;
Integer getCount();
<select id="getCount" resultType="int">
SELECT count(1) FROM t_user;
</select>
查询到Map
查询的结果没有对应的实体类的时候,就可以使用Map
集合。
resultType
设置成 map
即可。
查询为null 的字段是不会放到Map集合里面。
Map<String, Object> getUserById(@Param("id") String id);
<select id="getUserById" resultType="map">
SELECT * FROM t_user WHERE id = ${id}
</select>
查询到多条记录(没有对应实体类的)
可以使用 Map
存放多条记录,需要使用到 @MapKey
注解
@MapKey("id")
Map<String, Object> getAllUserToMap();
<select id="getAllUserToMap" resultType="map">
SELECT * FROM t_user;
</select>
// Map<id, User>
{
3={password=123, gender=男, id=3, age=23, email=12345@qq.com, username=root},
4={password=123456, gender=男, id=4, age=23, email=12345@qq.com, username=admin},
5={password=123456, gender=男, id=5, age=23, email=12345@qq.com, username=admin},
6={password=123456, gender=男, id=6, age=20, email=geek_zh@163.com, username=zs}
}
List<Map>
类型存放
List<Map<String, Object>> getAllUser();
<select id="getUserById" resultType="map">
SELECT * FROM t_user;
</select>
// List<Map<String, Object>>
[
{password=123, gender=男, id=3, age=23, email=12345@qq.com, username=root},
{password=123456, gender=男, id=4, age=23, email=12345@qq.com, username=admin},
{password=123456, gender=男, id=5, age=23, email=12345@qq.com, username=admin},
{password=123456, gender=男, id=6, age=20, email=geek_zh@163.com, username=zs}
]
特殊SQL
模糊查询
List<User> getUserByLike(@Param("mohu") String mohu);
<select id="getUserByLike" resultType="user">
SELECT * FROM t_user WHERE username like '%${mohu}%'
</select>
<select id="getUserByLike" resultType="user">
SELECT * FROM t_user WHERE username like CONCAT('%', #{mohu}, '%');
</select>
<!-- 第三种方式:用的最多的方式的 -->
<select id="getUserByLike" resultType="user">
SELECT * FROM t_user WHERE username like "%"#{mohu}"%"
</select>
批量删除 in
即传入的参数数量不确定 这里直接先构成一个字符串,再使用拼接方式传入即可。
int deleteMore(@Param("ids") String ids);
<!-- ids = "1,2,3" -->
<delete id="deleteMore" resultType="int">
DELETE FROM t_user WHERE id in (${ids})
</delete>
动态表名
表查询的字段相同,但是表名称不相同
List<User> getUserList(@Param("tableName") String tableName);
<select id="getUserList" resultType="user">
SELECT * FROM ${tableName};
</select>
获取自增主键
场景:比如一个班级表和一个学生表,在一个事务当中先创建一个班级(使用的是自增的主键)然后在这个班级里面添加若干的学生。
useGeneratedKeys
表示当前添加功能使用自增的主键keyProperty
将添加的数据的自增主键为实体类参数的属性赋值
Integer insertUser(User user);
<insert id="insertUser" resultType="int" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user VALUES(NULL, #{username}, #{password}, #{age}, #{gender}, #{email});
</insert>
ResultMap
字段名和属性名不一致(可以尝试别名处理)
一对一或一对多的关系查询.
字段名和属性名不一致的情况
方式一:查询的时候使用别名(在xml中修改原生的sql语句,非常麻烦)
方式二:开启下划线 → 小驼峰的配置,在 mybatis-config.xml
文件当中添加如下配置。(常用)
<settings>
<!-- 下划线 自动映射 驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
方式三:使用 ResultMap
, 注意 resultMap
和 resultType
是二选一的。
resultMap
的子元素
id
处理主键和属性字段的映射关系result
处理普通字段和属性的映射关系
resultMap
的子元素的属性
- column 对应的数据库中的字段
- property 对应的实体类中的字段
Emp getEmpByEmpId(@Param("empId") Integer empId);
<mapper namespace="com.atguigu.mybatis.mapper.EmpMapper">
<!-- 相同的属性和名称不用写也行的 -->
<resultMap id="empResultMap" type="Emp">
<!-- id 处理主键和属性字段的映射关系 -->
<id column="emp_id" property="empId"></id>
<!-- result 处理普通字段和属性的映射关系 -->
<result column="emp_name" property="empName"></result>
</resultMap>
<!-- Emp getEmpByEmpId(@Param("empId") Integer empId); -->
<select id="getEmpByEmpId" resultMap="empResultMap">
SELECT * FROM t_emp WHERE emp_id = #{empId};
</select>
</mapper>
多对一关系
多个Emp
对应一个 Dept
public class Emp {
private Integer empId;
private String empName;
private Integer age;
private String gender;
private Dept dept;
// getter/setter/toString
}
方式一:级联方式处理 ,即一次连接出所有字段。
Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);
<resultMap id="getEmpAndDeptByEmpId" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!-- 级联方式:多对一的映射关系 -->
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
SELECT *
FROM t_emp
LEFT JOIN t_dept
ON t_emp.dept_id = t_dept.dept_id
WHERE emp_id = #{empId};
</select>
方式二: association
标签。
association
: 专门处理多对一、一对一的映射关系(处理实体类类型的属性)。property
: 设置需要处理映射关系的属性的属性名。javaType
: 设置要处理的类型。
<resultMap id="getEmpAndDeptByEmpId" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!-- association : 专门处理多对一、一对一的映射关系 -->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
SELECT *
FROM t_emp
LEFT JOIN t_dept
ON t_emp.dept_id = t_dept.dept_id
WHERE emp_id = #{empId};
</select>
方式三:分步查询,先查询出Emp
,再根据Emp
中deptId
查询 Dept
优点:可以延迟加载,但必须在核心配置文件设置全局配置,具体配置看配置文件的settings。
- EmpMapper.java
/**
* 分步查询的第一步
* @param empId
* @return
*/
Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
- DeptMapper.java
/**
* 分步查询的第二步
* @param deptId
* @return
*/
Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
- EmpMapper.xml
<resultMap id="getEmpAndDeptByStepResultMap" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!--
property: 关联的类型
fetchType: eager 表示全局配置了懒加载,但是这里我还是想立即加载 如果是lazy表示延迟加载
select: 第二步查询的唯一标识 去找对应方法的引用
column: 第一步查询出来的外键
-->
<association property="dept" fetchType="eager"
select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id" ></association>
</resultMap>
<!-- Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId); -->
<select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepResultMap">
SELECT * FROM t_emp WHERE emp_id = #{empId};
</select>
- DeptMapper.xml
<!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
这里没有使用 ResultMap, 因为开启了 下划线 转 驼峰的配置
-->
<select id="getEmpAndDeptByStepTwo" resultType="dept">
SELECT * FROM t_dept WHERE dept_id = #{dept_id};
</select>
一对多关系
一个 Dept 对应多个 Emp
public class Dept {
private Integer deptId;
private String deptName;
private List<Emp> emps;
// getter/setter/toString
}
方式一:collection
Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId);
<resultMap id="getDeptAndEmpsByDeptIdResultMap" type="dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<!--
property: 关联的属性.
ofType: 集合内部元素的类型.
-->
<collection property="emps" ofType="emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
</collection>
</resultMap>
<!-- Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpsByDeptId" resultMap="getDeptAndEmpsByDeptIdResultMap">
SELECT *
FROM t_dept
LEFT JOIN t_emp
ON t_dept.dept_id = t_emp.dept_id
WHERE t_dept.dept_id = #{deptId};
</select>
方式二:分步查询,先查询部门,再根据部门的id查询部门下的员工
- DeptMapper.java
/**
* 分步查询 查询部门以及部门中的员工的信息
* @param deptId
* @return
*/
Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId);
- EmpMapper.java
/**
* 分布查询的第二步:查询出一个部门下的员工
* @param empId
* @return
*/
Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
- DeptMapper.xml
<resultMap id="getDeptAndEmpdsStepOneResultMap" type="dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<!-- fetchType="eager" 关闭懒加载 -->
<collection property="emps" fetchType="eager" ofType="emp"
select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpdsStepTwo"
column="dept_id">
</collection>
</resultMap>
<!-- Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpdsStepOne" resultMap="getDeptAndEmpdsStepOneResultMap">
SELECT *
FROM t_dept
WHERE dept_id = #{deptId};
</select>
- EmpMapper.xml
<!-- Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
这里没有使用resultMap, 因为已经开启了下划线转驼峰的配置
-->
<select id="getDeptAndEmpdsStepTwo" resultType="emp">
SELECT *
FROM t_emp
WHERE emp_id = #{empId};
</select>
动态SQL
根据传入的参数动态的决定最后执行的SQL语句。
if
,trim
,where
- if 条件是否成立,成立则接上
- where 自动会补充where或者去掉SQL语句开头的and
- trim
- prefix 前缀
- suffix 后缀
- suffixOverrides 后缀删除
- prefixOverrides 前缀删除
<!-- List<Emp> getEmpByCondition(Emp emp); -->
<select id="getEmpByCondition" resultType="emp">
SELECT *
FROM t_emp
<trim prefix="WHERE" suffixOverrides="AND">
<if test="empName != null and empName != ''">
emp_name LIKE "%"#{empName}"%" AND
</if>
<if test="age != null and age != ''">
age = #{age} AND
</if>
<if test="gender != null and gender != ''">
gender = #{gender} AND
</if>
</trim>
</select>
choose
, when
, otherwise
(switch
)
这相当于 if… else… 结构,所以一旦上面的when不成立,下面的语句就不会执行了。
<!-- List<Emp> getEmpByChoose(Emp emp); -->
<select id="getEmpByChoose" resultType="emp">
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>
</choose>
</where>
</select>
foreach
(批量插删)重要
经常用于批量添加和批量删除
- collection 设置要循环的数组或者集合
- item 用一个字符串数组或集合中的每一个数据
- separator 间隔符号
- open 循环的所有内容以什么开始
- close 循环的所有内容以什么结束
- 批量插入:
<!-- void insertMoreEmp(@Params("emps") List<Emp> emps); -->
<insert id="insertMoreEmp">
INSERT INTO t_emp
VALUES
<foreach collection="emps" item="emp" separator=",">
(NULL, #{emp.empName}, #{emp.age}, #{emp.gender}, null)
</foreach>
</insert>
- 批量删除:
<!-- void deleteMoreEmp(@Param("empIds") Integer[] empIds); -->
<delete id="deleteMoreEmp">
DELETE FROM t_emp
WHERE emp_id
IN
<foreach collection="empIds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>
SQL片段
将重复的SQL代码抽取出来被多个语句重复使用
<sql id="empColumns">
emp_id, emp_name, age, gender, dept_id
</sql>
<!-- List<Emp> getEmpByCondition(Emp emp); -->
<select id="getEmpByCondition" resultType="emp">
SELECT <include refid="empColumns"></include>
FROM t_emp
<trim prefix="WHERE" suffixOverrides="AND">
<if test="empName != null and empName != ''">
emp_name LIKE "%"#{empName}"%" AND
</if>
<if test="age != null and age != ''">
age = #{age} AND
</if>
<if test="gender != null and gender != ''">
gender = #{gender} AND
</if>
</trim>
</select>
缓存
一级缓存
一级缓存是SqlSession级别的缓存(即同一个链接),通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据就会从缓存中直接获取,不会从数据库重新访问。
以下四种情况会导致一级缓存失效:
不同SqlSession对应不同一级缓存。(即不同SqlSession,即使是相同查询条件也无用)。
同一个SqlSession但是查询条件不同。
同一个SqlSession 两次查询期间执行了任何一次针对此表增删改操作。
同一个SqlSession两次查询期间手动清空了缓存。
sqlSession.clearCache();
二级缓存
SqlSessionFactory
级别的,多个SqlSession
可以共享。
开启(四个条件)
- 核心配置文件,设置全局属性配置
cacheEnabled="true"
, 默认为true
,不需要设置
<settings>
<setting cacheEnabled="true"></setting>
</settings>
- 在映射文件中设置
<cache />
<mapper namespace="com.atguigu.mybatis.mapper.DynamicMapperSQLMapper">
<cache />
<!-- 余下代码 -->
</mapper>
- 二级缓存必须是在
SQLSession
关闭或提交之后有效。(即SQLSession
关闭或提交后,一级缓存当中的数据才会保存到一级缓存中) - 实体类必须实现
Serializable
接口。
二级缓存失效
只有一种情况
- 任意一次增删改会清空二级缓存。
二级缓存相关配置(简单了解即可)
在mapper
配置文件中添加的cache
标签可以设置一些厲性
eviction
属性:缓存回收策略,默认的是LRU
。LRU
(Least Recently Used) 最近最少使用的:移除最长时间不被使用的对象。FIFO
(First in First out) 先进先出:按对象进入缓存的顺序来移除它们。SOFT
软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK
弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval
属性:刷新间隔,单位秒- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size
:引用数目,正整数(一般不设置,使用默认的即可)- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly
:只读,true
/false
true
:只读缓存,会给所有调用者返回缓存对家的相同实例。因此这些对象不能被修改。这提供了很大的性能优势。false
:读写缓存,返回缓存对象的拷贝(通过序列化),会慢一些,但是安全,所以默认这一选项是false
。
缓存查询顺序
由大缓存到小缓存。
- 先查询二级缓存,因为二级缓存当中可能会有其他线程已经查询出来的数据。
- 二级缓存没有命中,则再查询一级缓存。
- 一级缓存也没有命中,则执行查询数据库。
SQLSession
关闭之后,一级缓存当中的数据会写入到二级缓存。
整合第三方缓存 EHCache
简单了解即可。
只针对二级缓存。
逆向
⚠️ 一定注意,如果数据库修改了,要重新逆向生成的话,一定要先把之前生成的文件全部删除才行!!!不然他默认是追加不是覆盖!!!
- 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate 是支持正向工程的。
- 逆向工程:先创建数据库表,由框架负责根据数据库表反向生成如下资源:
- Java实体类。
- Mapper接口。
- Mapper映射文件。
BUG
可能会出现java: 错误: 不支持发行版本 5
的报错,此时需要注意编译环境全部都要统一
⚠️ 初步看来 jdk18
会报错,统一用jdk1.8
,lauguage level 7
。
Project的jdk版本
Module的jdk版本
Java Compiler的jdk版本
添加依赖和插件
<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.atguigu.mybatis</groupId>
<artifactId>mybatis_mbg</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 依赖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.28</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>orgenerator</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.28</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
创建Mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"></properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name=""/>
</typeAliases>
<!--配置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--数据源-->
<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>
</environments>
<!--引入mybatis的映射文件-->
<mappers>
<package name=""/>
</mappers>
</configuration>
创建逆向工程的核心配置文件
targetRuntime
:选择生成的清晰简洁/奢华尊享MyBatis3
奢华尊享 基本所有情况都可以MyBatis3Simple
清晰简洁 只会写明单表
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//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"
serverTimezone=UTC"
connectionURL="jdbc:mysql://localhost:3306/mybatis?
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>
分页插件
pom.xml
添加依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置分页插件
在mybatis-config.xml
核心配置文件添加 plugin
<?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>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<plugins>
<!-- 设置分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
测试分页功能
@Test
public void testPage() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DynamicMapperSQLMapper mapper = sqlSession.getMapper(DynamicMapperSQLMapper.class);
// 查询功能之前开启分页, 第1页, 每页显示4条, 并且获取部分分页的信息
// Page{count=true, pageNum=2, pageSize=4, startRow=4, endRow=8, total=0, pages=0, reasonable=null, pageSizeZero=null}[]
Page<Object> page = PageHelper.startPage(2, 4);
System.out.println(page);
// 查询
List<Emp> emps = mapper.getEmpByCondition(null);
//输出分页信息
// Page{count=true, pageNum=2, pageSize=4, startRow=4, endRow=8, total=25, pages=7, reasonable=false, pageSizeZero=false}[Emp{empId=5, empName='刘三', age=22, gender='男'}, .....]
System.out.println(page);
// 输出数据
emps.forEach(System.out::println);
// 生成导航页码信息, 更丰富页面信息
PageInfo<Emp> pageInfo = new PageInfo<>(emps, 5);
sqlSession.close();
}
关注上方的PageInfo
即可,内部封装了很多分页相关的信息。