目录
一、简介
1.1 什么是MyBatis
- Mybatis是apache的一个开源项目iBatis。2010年这个项目由 apache software foundation 迁移到了 google code ,并且改名为 MyBatis。2013年11月迁移到 Github
- Mybatis是一款优秀的持久层矿框架
- 它支持定制化 SQL、存储过程以及高级映射
- Mybatis避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- Mybatis可以使用简单的 XML 或者注解来配置和映射原生类型、接口和 JAVA 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
1.2 如何获得Mybatis
- maven仓库:
<!--mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
- Github:https://github.com/mybatis/mybatis-3/releases
- 中文文档:https://mybatis.org/mybatis-3/zh/index.html
1.3 什么是持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(jdbc),io文件持久化
- 生活中:冷藏、罐头
为什么要持久化?
- 有些对象,不能让他丢失
- 内存贵
1.4 持久层
- 完成持久化工作的代码块
- 层的界限十分明显
1.5 为什么需要Mybatis
- 帮助程序员将数据存入到数据库中
- 方便、简化、框架
- 传统的 JDBC 代码太复杂
- 自动化
- Mybatis 并不是必须的,只是更容易上手
- 使用的人多
优点:
- 简单易学
- 灵活
- sql 和代码分离,提高了可维护性
- 提供映射标签,支持对象与数据库的 orm 字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供 xml 标签,支持编写动态 sql
二、构建Mybatis程序
思路:搭建环境 --> 导入Mybatis --> 编写代码 --> 测试
2.1 搭建环境
- 搭建数据库
-- 删除同名数据库
DROP DATABASE IF EXISTS `mybatis`;
-- 建库
CREATE DATABASE IF NOT EXISTS `mybatis`;
USE `mybatis`;
-- 建表
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 插入数据
INSERT INTO `user` VALUES
(1,'张三','123456'),
(2,'李四','123457'),
(3,'王五','123458')
- 新建项目
1、新建一个普通的 maven 项目
2、删除 src
3、导入 maven 依赖
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 创建一个模块
- 编写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核心配置-->
<configuration>
<environments default="development">
<!--开发环境-->
<environment id="development">
<!--事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="数据库用户名"/>
<property name="password" value="数据库密码"/>
</dataSource>
</environment>
</environments>
</configuration>
- 编写mybatis工具类
/**
* 会话工厂的创建类->会话工厂->会话
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用Mybatis第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//有了sqlSessionFactory,顾名思义,我们就可以从中获取SqlSession的实例了
//SqlSession包含了面向数据库执行sql命令所需的所有方法
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
2.3 编写代码
- 实体类
public class User {
private Integer id;
private String name;
private String pwd;
public User() {
}
public User(Integer id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
- Dao接口
public interface UserMapper {
//查询所有用户
List<User> getUserList();
}
- 接口实现类由原来的impl转换为一个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" >
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cary.mapper.UserMapper">
<!--查询所有用户-->
<select id="getUserList" resultType="com.cary.pojo.User">
select * from user
</select>
</mapper>
- 整个项目结构图
2.4 测试
- 编写测试类(注意包的层级)
package com.cary.test;
import com.cary.mapper.UserMapper;
import com.cary.pojo.User;
import com.cary.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserTest {
@Test
public void testGetUserList(){
//第一步:获得sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:
//执行sql,通过class对象调用其内查询方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//方式二:
//根据方法的返回值调用方法
//List<User> userList = sqlSession.selectList("com.cary.mapper.UserMapper.getUserList");
List<User> userList = mapper.getUserList();
userList.forEach(System.out::println);
//关闭sqlSession
sqlSession.close();
}
}
- 易报错:org.apache.ibatis.binding.BindingException: Type interface com.cary.mapper.UserMapper is not known to the MapperRegistry
解决:应该在Mybatis核心配置文件mybatis-config.xml中加载sql映射文件
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<package name="com.cary.mapper"/>
</mappers>
- 运行结果
2.5 容易遇到的问题
- 配置文件没有注册
- 绑定接口错误
- 方法名不对
- 返回类型不对
- Maven 导出资源问题
三、CRUD
- 编写接口
public interface UserMapper {
//查询所有用户
List<User> getUserList();
//根据ID查询用户
User getUserById(Integer id);
//添加一个用户
int addUser(User user);
//修改一个用户
int updateUser(User user);
//删除一个用户
int deleteUserById(Integer id);
}
- 编写对应的 mapper 中的 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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cary.mapper.UserMapper">
<!--添加一个用户,对象中的属性可以直接取出来-->
<insert id="addUser" parameterType="com.cary.pojo.User">
insert into `user` values (#{id},#{name},#{pwd})
</insert>
<!--修改一个用户-->
<update id="updateUser" parameterType="com.cary.pojo.User">
update `user` set `name`=#{name},pwd=#{pwd} where id=#{id}
</update>
<!--根据ID删除一个用户-->
<delete id="deleteUserById" parameterType="Integer">
delete from `user` where id=#{id}
</delete>
<!--查询所有用户-->
<select id="getUserList" resultType="com.cary.pojo.User">
select * from user
</select>
<!--根据ID查询用户-->
<select id="getUserById" resultType="com.cary.pojo.User" parameterType="Integer">
select * from user where id=#{id}
</select>
</mapper>
- 测试
public class UserTest {
//查询所有
@Test
public void testGetUserList(){
//第一步:获得sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:
//执行sql,通过class对象调用其内查询方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//方式二:
//根据方法的返回值调用方法
//List<User> userList = sqlSession.selectList("com.cary.mapper.UserMapper.getUserList");
List<User> userList = mapper.getUserList();
userList.forEach(System.out::println);
//关闭sqlSession
sqlSession.close();
}
//根据ID查询
@Test
public void testGetUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
sqlSession.close();
System.out.println(user);
}
//添加一个用户,增删改需要提交事务
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.addUser(new User(4, "赵四", "152346"));
System.out.println(row+"行受到影响");
//提交事务
sqlSession.commit();
sqlSession.close();
}
//修改一个用户,增删改要提交事务
@Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.updateUser(new User(4, "赵三", "456789"));
System.out.println(row+"行受到影响");
//提交事务
sqlSession.commit();
sqlSession.close();
}
//根据ID删除一个用户,增删改要提交事务
@Test
public void testDeleteUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.deleteUserById(4);
System.out.println(row+"行受到影响");
//提交事务
sqlSession.commit();
sqlSession.close();
}
}
3.1 namespace
namespace 中的包名要和 Dao/Mapper 接口的包名一致!!!
3.2 select
选择,查询语句;
- id:就是对应的 namespace 中的方法名
- resultType:sql 语句执行的返回值
- parameterType:参数类型
3.3 Insert
插入语句;
- id:就是对应的 namespace 中的方法名
- resultType:sql 语句执行的返回值
- parameterType:参数类型
3.4 Update
更新,修改语句;
- id:就是对应的 namespace 中的方法名
- resultType:sql 语句执行的返回值
- parameterType:参数类型
3.5 Delete
删除语句;
- id:就是对应的 namespace 中的方法名
- resultType:sql 语句执行的返回值
- parameterType:参数类型
注意:增删改需要提交事务
3.6 万能Map
关于万能Map的使用本人另外整理了一篇文章:Mybatis中的万能Map
3.7 模糊查询
<select id="xxx" resultType="com.cary.pojo.User">
select * from `user` where name like "%"#{name}"%"
</select>
四、配置解析
4.1 核心配置文件
- mybatis-config.xml
- MyBatis 的配置文件包含了影响 MyBatis 行为的设置和属性信息
configura(配置)√
properties(属性)√
settings(设置)√
typeAliases(类型别名)√
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)√
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商表示)
mappers(映射器)√
4.2 环境配置(environments)
MyBatis 可以配置成适应多种环境
要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
学会使用配置多套运行环境!
Mybatis默认的事务管理器就是JDBC,连接池:POOLED
4.3 属性(properties)
可以通过 properties 属性来实现引用配置文件
这些属性都是可以外部配置且可以动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递【db.properties】
- 编写一个配置文件 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=数据库用户名
password=数据库密码
- 在核心配置文件中引入外部配置文件
<!--引入配置文件-->
<properties resource="db.properties"/>
可以直接引入外部文件
可以在其中增加一些属性配置
如果两个文件有同一个字段,优先使用外部配置文件的!!!
4.4 类型别名(typeAliases)
- 类型别名是为 Java 类型设置一个短的名字
- 存在的意义仅在于用来减少类完全限定名的冗余
- 可以给实体类起别名
<!--给实体类起别名-->
<typeAliases>
<typeAlias type="com.cary.pojo.User" alias="User"/>
</typeAliases>
- 也可以指定一个报名,Mybatis 会在包名下面搜索需要的 JavaBean ,扫描实体类的包,他的默认别名就为这个类的类名首字母小写!!!
<!--指定包名-->
<typeAliases>
<package name="com.cary.pojo"/>
</typeAliases>
如果实体类少的时候,使用第一种
如果实体类多的时候,建议使用第二种
第一种可以DIY别名,第二种如果非要改,需要在实体类上增加注解 @Alias
4.5 设置(settings)
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true/false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true/false | false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true/false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J、LOG4J(3.5.9 起废弃)、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING | 未设置 |
4.6 映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件
- 方式一:【推荐使用】
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/cary/mapper/UserMapper.xml" />
</mappers>
- 使用class文件绑定注册
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper class="com.cary.mapper.UserMapper" />
</mappers>
注意点:
- 接口和它的 Mapper 配置文件必须同名!
- 接口和它的 Mapper 配置文件必须在同一个包下!
- 方式三:使用扫描包进行注入绑定
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<package name="com.cary.mapper" />
</mappers>
注意点:
- 接口和它的 Mapper 配置文件必须同名!
- 接口和它的 Mapper 配置文件必须在同一个包下!
五、解决属性名和字段名不一致的问题
- 数据库中字段
- 实体类中的字段不一致
- 测试出现问题
- 原因
查询语句:select * from user where id=#{id}
实质是:select id,name,pwd from user where id=#{id}
数据库中字段和实体类字段无法对应传值
起别名(不推荐)
- 给字段名取别名,让别名与实体类的属性名一致
select id,name,pwd as password from user where id=#{id}
手动创建表的字段名与实体类的属性名之间的映射(字段多、麻烦)
- resultMap结果集映射
- < id >:映射表的主键
- < result >:映射表中其他的字段
- column 数据库中的字段
- property实体类中的属性
- 查询结果使用 resultMap ,而不是 resultType
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column数据库中的字段,property实体类中的属性-->
<!--result column="id" property="id"/-->
<!--result column="name" property="name"/-->
<result column="pwd" property="password"/>
</resultMap>
<!--根据ID查询用户-->
<select id="getUserById" resultMap="UserMap">
select * from user where id=#{id}
</select>
- resultMap 元素是MyBatis中最重要最强大的元素
- resultMap 的设计思想,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就好了
- 什么不一样转什么
在核心配置文件中直接将下划线映射成驼峰命名法(强烈推荐)
前提是实体类的属性名和字段名有对应关系,如 user_name 和 userName
- 在核心配置文件中设置命名映射关系
<settings>
<!--映射下划线为驼峰命名法-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
六、多条件查询
- < where >标签
有条件的时候,相当于 where 关键字
没有条件的时候,会去掉多余的 where 关键字
还会去掉多余的 and 和 or 关键字 - < if >标签
test 属性:逻辑判断
方式一:参数都写在方法的形参上
如果有多个参数作为占位符来使用,必须给每个参数通过注解 @Param 起名字
List<User> findByCondition(@Param("status") Integer status,
@Param("age") Integer age,
@Param("name") String userName);
<!-- sql语句中的变量和形参名字无关 -->
<select id="findByCondition" resultType="User">
select * from `user` where `status`=#{status} and `age`=#{age} and `name` like "%"#{name}"%"
</select>
方式二:将多个参数封装到实体类中
实体类中的属性名字与条件查询的名字相同即可
List<User> findByCondition(User user);
方式三:万能Map
Map中键的名字和条件查询的名字相同
List<User> findByCondition(Map<String,Object> map);
多分支选择其中一个
- < choose >标签
多条件选择的父标签,包含以下两个子标签
< when > 多个条件之一,可以出现多个,如果条件为真则执行,其他的就不执行
< otherwise> 如果上面所有条件都不满足,这执行这个分支,类似于switch里的default
<choose>
<when test="...">
...
</when>
<otherwise></otherwise>
</choose>
七、复杂查询处理
- 多个学生对应一个老师
- 对于学生这边而言,关联…多个学生,关联一个老师【多对一】
- 对于老师这边而言,集合…一个老师,有很多个学生【一对多】
测试环境搭建
- 新建工程、导入lombok依赖
<dependencies>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
- 数据库
DROP TABLE IF EXISTS `user`;
-- 创建用户基本表
CREATE TABLE `user` (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
birthday DATE,
sex CHAR(1) DEFAULT '男',
address VARCHAR(50)
);
INSERT INTO `user` VALUES (NULL, '孙悟空','1980-10-24','男','花果山水帘洞');
INSERT INTO `user` VALUES (NULL, '白骨精','1992-11-12','女','白虎岭白骨洞');
INSERT INTO `user` VALUES (NULL, '猪八戒','1983-05-20','男','福临山云栈洞');
INSERT INTO `user` VALUES (NULL, '蜘蛛精','1995-03-22','女','盤丝洞');
SELECT * FROM `user`;
-- 用户扩展信息表,一个用户对应一条用户扩展信息
CREATE TABLE user_info (
id INT PRIMARY KEY, -- 既是主键又是外键
height DOUBLE, -- 身高厘米
weight DOUBLE, -- 体重公斤
married TINYINT, -- 是否结婚,1为结婚,0为未婚
FOREIGN KEY (id) REFERENCES `user`(id)
);
-- 插入用户扩展信息表
INSERT INTO user_info VALUES(1,185,90,1),(2,170,60,0);
SELECT * FROM user_info;
- 实体类
/**
* 用户实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 建立一对一的关联关系
private UserInfo userInfo;
//只输出用户的基本信息
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
/**
* 用户信息表
* 与用户表是1对1的关系
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private Integer id;
private Double height;
private Double weight;
private Boolean married;
@Override
public String toString() {
return "UserInfo{" +
"id=" + id +
", height=" + height +
", weight=" + weight +
", married=" + married +
'}';
}
}
- 项目整体结构
7.1 表连接查询
一对一查询
- 接口
// 一对一:通过用户ID查找用户和扩展信息
User findUserAndInfo(Integer id);
- mapper.xml
<!--用户表的映射
id:唯一标识,给映射起个名字,只要不重复就可以了
type:指定要映射的实体类的名字
autoMapping:自动映射,只要字段名和属性名相同则自动映射
表连接查询无论字段名是否相同,都要进行映射,否则没有值
<association>:创建一对一的关系映射
property:指定另一方对象的属性名字
resultMap:指定另一方的映射
-->
<resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
<association property="userInfo" resultMap="userInfoMap"/>
</resultMap>
<!--用户扩展信息表映射-->
<resultMap id="userInfoMap" type="com.cary.pojo.UserInfo" autoMapping="true"/>
<!-- 一对一表连接查询
使用resulMap指定映射的名字
-->
<select id="findUserAndInfo" resultMap="userMap">
select * from user u inner join user_info i on u.id=i.id where u.id=#{id}
</select>
- 表连接查询无论字段名是否相同,都要进行映射,否则没有值
- resultMap
id:唯一标识,给映射起个名字,只要不重复就可以
type:指定要映射的实体类的名字
autoMapping:自动映射,只要字段名和属性名相同则自动映射 - 子标签 < association >:创建一对一的关系映射
property:指定另一方的属性名字
resultMap:指定另一方的映射
一对多映射
- 数据准备
-- 创建订单表
CREATE TABLE `order`(
oid INT PRIMARY KEY AUTO_INCREMENT , -- 主键
user_id INT NOT NULL, -- 用户id,外键
number VARCHAR(20), -- 订单编号
create_time DATETIME, -- 下单时间
note VARCHAR(100) -- 备注
);
-- 添加订单数据
INSERT INTO `order` VALUES(NULL, 1,'10001001', NOW(), '小米9代'),(NULL, 1,'10001002', NOW(), '9代小米'),(NULL, 1,'10001003', NOW(), '小米9手机');
INSERT INTO `order` VALUES(NULL, 2,'10001004', NOW(), '逃生锤'),(NULL, 2,'10001005', NOW(), '安全带');
SELECT * FROM `order`;
- 实体类
/**
* 用户实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//建立一对多的关联系统
private List<Order> orders;
// 建立一对一的关联关系
private UserInfo userInfo;
//只输出用户的基本信息
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
/**
* 订单实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer oid; // 主键
private Integer userId; // 用户id,外键
private String number; // 订单编号
private Timestamp createTime; // 下单时间,既有日期又有时间
private String note; // 备注
@Override
public String toString() {
return "OrderForm{" +
"oid=" + oid +
", userId=" + userId +
", number='" + number + '\'' +
", createTime=" + createTime +
", note='" + note + '\'' +
'}';
}
}
- 接口
// 一对多:通过用户id查询用户和他的订单信息
User findUserAndOrders(Integer userId);
- mapper.xml
<!--用户表的映射
id:唯一标识,给映射起个名字,只要不重复就可以了
type:指定要映射的实体类的名字
autoMapping:自动映射,只要字段名和属性名相同则自动映射
表连接查询无论字段名是否相同,都要进行映射,否则没有值
<collection>:创建一对多的关系映射
property:指定另一方对象的属性名字
resultMap:指定另一方的映射
-->
<resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
<!--一对多必须添加主键的配置,通过主键去查询另一张表的数据,封装到集合中去-->
<id column="id" property="id"/>
<!--一对多-->
<collection property="orders" resultMap="orderMap"/>
</resultMap>
<!--订单映射-->
<resultMap id="orderMap" type="com.cary.pojo.Order" autoMapping="true"/>
<!--一对多:通过用户id查询用户和他的订单信息-->
<select id="findUserAndOrders" resultMap="userMap">
select * from user u inner join `order` o on o.user_id=u.id where u.id=#{userId}
</select>
- 表连接查询无论字段名是否相同,都要进行映射,否则没有值
- resultMap
id:唯一标识,给映射起个名字,只要不重复就可以
type:指定要映射的实体类的名字
autoMapping:自动映射,只要字段名和属性名相同则自动映射 - 子标签 < collection>:创建一对多的关系映射
property:指定另一方的属性名字
resultMap:指定另一方的映射 - 一对多必须添加主键的配置,通过主键去查询另一张表的数据,封装到集合中去
< id column=“id” property=“id”/>
column:数据表的列名或者标签别名
property:需要映射到实体类的属性名称
小结
与一对一的关联映射是一样的,唯一的区别是
- 一对一:association
- 一对多:collection
扩展:其实上面2个标签的功能是一样的,只是语义上区别
7.2 级联查询
表连接查询是直接查出两张表的数据
级联查询是指先查询一张表,再查询另一张表,分开查询
- 接口写两个方法
public interface UserMapper {
// 通过用户ID查询用户的基本信息
User findUserById(Integer id);
// 通过用户ID查询用户的扩展信息
UserInfo findUserInfoById(Integer id);
}
- mapper.xml
<mapper namespace="com.cary.mapper.UserMapper">
<resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
<!-- 主键的映射 -->
<id property="id" column="id"/>
<!-- 一对一级联查询 -->
<association property="userInfo" select="findUserInfoById" column="id"/>
</resultMap>
<!-- 通过用户ID查询用户的基本信息 -->
<select id="findUserById" resultMap="userMap">
select * from `user` where id=#{id}
</select>
<!-- 通过用户ID查询用户的扩展信息 -->
<select id="findUserInfoById" resultType="com.cary.pojo.UserInfo">
select * from `user_info` where id=#{id}
</select>
</mapper>
延迟加载
- 在mybatis核心配置文件mybatis-config.xml进行配置
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--不使用积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 开启延迟加载时,级联查询表现为第二张表的查询当需要时才会执行
- 注意:不要直接输出user对象,因为会触发toString()方法,导致立即加载userInfo对象
7.2 一对多级联查询
- 接口
public interface UserMapper {
// 通过用户ID查询用户的基本信息
User findUserById(Integer id);
// 通过用户ID查询用户的所有订单
List<Order> findOrders(Integer userId);
// 通过用户ID查询用户的扩展信息
UserInfo findUserInfoById(Integer id);
}
- mapper.xml
<mapper namespace="com.cary.mapper.UserMapper">
<resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
<!-- 主键的映射 -->
<id property="id" column="id"/>
<!-- 一对一级联查询 -->
<association property="userInfo" select="findUserInfoById" column="id"/>
<collection property="orders" select="findOrders" column="id"/>
</resultMap>
<!-- 通过用户ID查询用户的基本信息 -->
<select id="findUserById" resultMap="userMap">
select * from `user` where id=#{id}
</select>
<!-- 通过用户ID查询用户的扩展信息 -->
<select id="findUserInfoById" resultType="com.cary.pojo.UserInfo">
select * from `user_info` where id=#{id}
</select>
<!-- 通过用户ID查询用户的所有订单 -->
<select id="findOrders" resultType="com.cary.pojo.Order">
select * from `order` where user_id=#{ids}
</select>
</mapper>
一级缓存
- 一级缓存是默认打开的,会话级别缓存,只在一个会话中起作用
- 测试
public class CacheTest {
// 测试一级缓存
@Test
public void testFirst(){
// 获取会话对象
SqlSession session = MyBatisUtils.getSession();
// 得到接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 调用接口中方法
UserInfo info1 = userMapper.findUserInfoById(1);
System.out.println(info1);
// 第二次查询:从一级缓存中获取数据,没有再次发送SQL语句
UserInfo info2 = userMapper.findUserInfoById(1);
System.out.println(info2);
session.close();
}
}
- 结果
- 一级缓存的清空
清空的目的:如果进行了增删改的操作,表中的记录有可能发生变化,缓存中的数据就是脏数据
清空的方式:只要执行了增删改的操作,提交事务,关闭会话操作会就自动清空一级缓存的数据
二级缓存
- 二级缓存是mapper映射级别缓存,作用范围跨越SqlSession,即可以在多个SqlSession之间共享二级缓存数据
- 二级缓存关键点:实体类需要实现Serializable接口
- 实体类实现Serializable接口
- 在核心配置文件开启二级缓存
<settings>
<!--在控制台显示SQL语句-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--映射下划线为驼峰命名法-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--不使用积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在mapper.xml映射文件中
<mapper namespace="com.cary.mapper.UserMapper">
<!--开启二级缓存:对当前配置文件中所有的查询进行二级缓存-->
<cache/>
</mapper>
- 测试
public class CacheTest {
// 测试二级缓存:在同一个会话工厂中,在不同的会话中起作用
@Test
public void testSecond(){
// 获取会话对象
SqlSession session1 = MyBatisUtils.getSession();
// 得到接口的代理对象
UserMapper userMapper1 = session1.getMapper(UserMapper.class);
UserInfo userInfo1 = userMapper1.findUserInfoById(1);
System.out.println(userInfo1);
session1.close();
// 获取会话对象
SqlSession session2 = MyBatisUtils.getSession();
// 得到接口的代理对象
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
UserInfo userInfo2 = userMapper2.findUserInfoById(1);
System.out.println(userInfo2);
session2.close();
}
}
- 结果
八、添加记录
< insert > 标签的属性
- useGeneratedKeys:获取 mysql 新添加的主键,前提是逐渐自动增长
- keyColumn:表中主键字段名字
- keyProperty:实体类中主键的属性名字
九、修改记录
< set > 标签
- 生成 set 关键字
- 根据条件去掉多余的逗号
十、删除记录
批量删除sql如:
delete from 表名 where id in (id,id,id...)
< delete > 标签
用于遍历数组或集合
子标签 < foreach > 属性
- collection:指定要遍历的数组或集合名字,需要使用 @Param 指定名字
- open:遍历前添加的字符,如:" ( "
- item:指定遍历中每个元素的变量名字,如上述的 " id "
- separator:每遍历一个元素添加的符号,如:" , "
- close:便利结束后添加的字符,如:" ) "
void deleteByIds(@Param("aaa") int[] ids)
<delete id="...">
delete from 表名 where id
in
<foreach collection="aaa" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
十一、mybatis参数传递
多个参数传递
我们 在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解起别名的情况下我们可以有一下命名规则:
- 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。
- 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。
arg从0开始,param从1开始
扩展
-
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域(也就是局部方法的变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在,以保证所有的XML解析资源可以被释放给更重要的事情。- 一旦创建就不再需要
- 局部变量
-
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。使用 SqlSessionFactory 最佳实践实在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码的“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或静态单例模式。- 类似于数据库连接池
- 一旦被创建就应该在应用的运行期间一直存在, 没有任何理由丢弃它或重新创建另一个实例
- 因此 SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用 单例模式 或者静态单例模式
-
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法的作用域。绝不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如Servlet框架中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession ,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。- 连接到连接池的一个请求!
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用域是请求或方法作用域
- 用完后应及时关闭,否则占用资源!
以下示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession sqlSession=sqlSessionFactory.openSession){
//你的逻辑代码
}finally {
sqlSession.close();
}