1. 为啥要用 MyBatis
我们作为一个程序员,主要工作归根结底就是和数据
打交道。而使用 java 操作数据库的原始方式就是 JDBC
。
先看看使用 JDBC 方式是如何操作数据库的:
// 1. 加载配置文件
Properties pro=new Properties();
pro.load(new FileReader("resource/jdbc.properties"));
// 2. 获取配置文件中连接数据库的信息
String url=pro.getProperty("url");
String user=pro.getProperty("user");
String password=pro.getProperty("password");
String driver=pro.getProperty("driver");
// 3. 加载数据库的驱动
Class.forName(driver);
// 4. 创建数据库的连接
Connection conn = DriverManager.getConnection(url, user, password);
// 5. sql 语句
String sql = "select * from s_admin where username=? and password=?";
// 3. 创建执行sql的对象
ps = conn.prepareStatement(sql);
// 4. 给 ?赋值
ps.setString(1, username);
ps.setString(2, password);
// 5. 执行sql
ResultSet rs = ps.executeQuery();
// 6. 如果查询出数据,则返回该条数据
if (rs.next()) {
Admin admin = new Admin();
admin.setUsername(rs.getString("username"));
admin.setPassword(rs.getString("password"));
return admin;
// 7. 否则返回空
} else {
return null;
}
看完上面的代码,我们发现了 JDBC 存在的问题:
1.每次操作我们都要创建 connection、Statement 等一些对象,操作完还要关闭、销毁这些对象。
2.ResultSet 不能帮我们完成数据库和实体对象的自动转换,我们还要手动赋值。
3.代码冗余,而且业务操作竟然和数据库操作混在一起,开发效率太低了。
真是越来越不想用 JDBC 了,那有没有一个玩意能帮我解决上面 JDBC 遇到的问题呢?
有的,盖世英雄MyBatis
踩着七彩祥云来了。
2. MyBatis 简介
官网地址:
https://mybatis.org/mybatis-3/
MyBatis 是一个基于 java 的持久层框架,它内部封装了 jdbc。
开发人员只需要关注 sql 语句,不需要处理加载驱动、创建连接等繁琐的过程。
MyBatis 通过 xml 或注解两种方式配置 sql 语句,然后执行 sql 并将结果映射为 java 对象并返回。
名词解释:
框架:
框架是系统中可复用的设计,其实就相当于一个封装了很多功能的工具。
持久层
:就是和数据库打交道的那一层。
3. 环境搭建
1.创建数据库和表
我们使用 Navicat 创建数据库 mybatis_demo,然后创建 user 表。
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
`sex` varchar(1) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '性别',
`age` int DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
2.创建 Maven 项目
File -> New -> Project -> Maven 引入依赖
<dependencies>
<!-- 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.27</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
</dependencies>
3.创建实体类
User 类:
public class User {
private int id;
private String name;
private String sex;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
4.创建 Dao 接口
public interface UserDao {
/**
* 获取所有用户信息
* @return
*/
List<User> getAll();
}
5.创建 sql 映射文件
这是一个 mybatis 使用的配置文件,专门用来写 sql 语句的。
它是一个 xml 文件,因为 sql 语句包含在 mapper 标签中,所以又叫 mapper 文件。一般来说一个表对应一个 xml 文件。
规定:
文件名称要和接口保持一致。
UserDao.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="com.xxl.dao.UserDao">
<select id="getAll" resultType="com.xxl.model.User">
select * from user
</select>
</mapper>
6.创建 MyBatis 主配置文件
主配置文件也是 xml 文件。
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>
<!-- 配置 mybatis 输出日志,可以打印 sql-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境-->
<environment id="mysql">
<!-- 事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源 POOLED:表示使用连接池 -->
<dataSource type="POOLED">
<!-- 配置连接数据库的信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置-->
<mappers>
<mapper resource="mapper/UserDao.xml"/>
</mappers>
</configuration>
7.编写测试类
public class UserTest {
@Test
public void testUser() throws IOException {
// 1.读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.创建 SqlSessionFactory 工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.获取 SqlSession 对象
SqlSession session = factory.openSession();
// 4.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 5.执行接口的方法
List<User> userList = userDao.getAll();
userList.forEach(user ->{
System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
});
}
}
8.执行结果 9.完整目录结构
4. MyBatisUtil
上面测试代码中读取配置文件、获取 sqlsession 对象等都是一些重复性的操作,我们可以将这些代码封装到一个工具类中。
MyBatisUtil:
public class MyBatisUtil {
// 定义 SqlSessionFactory
private static SqlSessionFactory factory;
// 使用静态块只创建一次 SqlSessionFactory
static {
try {
// 读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建 SqlSessionFactory 对象
factory = new SqlSessionFactoryBuilder().build(in);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取 SqlSession 对象
public static SqlSession getSqlSession() {
SqlSession sqlSession = factory.openSession();
return sqlSession;
}
// 提交事务
public static void commit(SqlSession sqlSession) {
if (null != sqlSession) {
sqlSession.commit();
}
close();
}
// 回滚事务
public static void rollBack(SqlSession sqlSession) {
if (null != sqlSession) {
sqlSession.rollback();
}
close();
}
// 关闭 SqlSession
public static void close() {
SqlSession sqlSession = getSqlSession();
if (null != sqlSession) {
sqlSession.close();
}
}
}
5. 增删改查
这里我们使用 MyBatisUtil 实现用户的增删改查操作。
UserDao:
public interface UserDao {
// 获取所有用户信息
List<User> getAll();
// 新增用户
boolean add(User user);
// 修改用户
boolean update(User user);
// 删除用户
boolean delete(int id);
}
UserDao.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="com.xxl.dao.UserDao">
<select id="getAll" resultType="com.xxl.model.User">
select * from user
</select>
<insert id="add" parameterType="com.xxl.model.User">
insert into user(name,sex,age) values(#{name},#{sex},#{age})
</insert>
<update id="update">
update user set name = #{name}, sex = #{sex},age = # {age} where id = #{id}
</update>
<delete id="delete">
delete from user where id = #{id}
</delete>
</mapper>
1.查询用户
@Test
public void testGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
List<User> userList = userDao.getAll();
userList.forEach(user ->{
System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
2.新增用户
@Test
public void add(){
SqlSession session = null;
try{
// 1.获取 SqlSession 对象
session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
User user = new User();
user.setName("张无忌");
user.setAge(31);
user.setSex("男");
userDao.add(user);
// 4.提交事务并关闭 SqlSession
MyBatisUtil.commit(session);
}catch (Exception e){
e.printStackTrace();
// 回滚事务
MyBatisUtil.rollBack(session);
}
}
3.修改用户
@Test
public void update(){
SqlSession session = null;
try{
// 1.获取 SqlSession 对象
session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
User user = new User();
user.setId(3);
user.setName("张无忌");
user.setAge(31);
user.setSex("男");
userDao.update(user);
// 4.提交事务并关闭 SqlSession
MyBatisUtil.commit(session);
}catch (Exception e){
e.printStackTrace();
// 回滚事务
MyBatisUtil.rollBack(session);
}
}
4.删除用户
@Test
public void delete(){
SqlSession session = null;
try{
// 1.获取 SqlSession 对象
session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
userDao.delete(3);
// 4.提交事务并关闭 SqlSession
MyBatisUtil.commit(session);
}catch (Exception e){
e.printStackTrace();
// 回滚事务
MyBatisUtil.rollBack(session);
}
}
6. MyBatist 核心配置文件
mybatis-config.xml 是 mybatis 的主配置文件,所有的配置都在 configuration 标签里面。
它主要包括:定义别名、配置数据源、配置 mapper 文件等。
1.定义别名
前面我们在 mapper 文件里面设置的 resultType 是全路径名。
<select id="getAll" resultType="com.xxl.model.User">
select * from user
</select>
为了简化代码,MyBatis 允许我们给全路径名起一个简单的别名,一般是实体类的类名。我们可以在 mybatis-config.xml 里面这样配置:
<!--配置别名-->
<typeAliases>
<package name="com.xxl.model"/>
</typeAliases>
上面 name 的值一般是实体类所在包的全路径,配置完之后我们就可以直接用 user 代替 com.xxl.model.User 了。
<select id="getAll" resultType="user">
select * from user
</select>
2.配置数据源
我们使用 dataSource 标签配置连接数据库的各种参数,其中 dataSource 的 type 表示使用连接池配置数据源。
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境-->
<environment id="mysql">
<!-- 事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源 POOLED:表示使用连接池 -->
<dataSource type="POOLED">
<!-- 配置连接数据库的信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
为了便于维护数据库的连接参数,我们一般会将这些参数存放在一个专门的文件中,MyBatis 主配置文件再从这个文件中读取连接数据库的参数数据。
在 resources 目录下面新建 jdbc.properties 文件 在主配置文件使用 properties 标签引入 jdbc.properties 文件
<properties resource="jdbc.properties"/>
修改 dataSource
<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>
3.配置查找 mapper 文件的路径
<mappers>
<mapper resource="mapper/UserDao.xml"/>
</mappers>
4.事务
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,其实就是 JDBC 的 Connection 对象的 commit(), rollback() 。
<transactionManager type="JDBC" />
type="JDBC" : 表示使用 JDBC 的事务管理机制。但是 MyBatis 默认将自动提交功能关闭了,改为了手动提交。
所以上面的测试代码中,我们在做完增删改之后还要手动提交事务。
自动提交事务
我们在获取 sqlSession 的时候,只需要将 openSession 的参数设置为 true,就能将 MyBatis 设置为自动提交事务。
// 读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 获取 SqlSession 对象
SqlSession sqlSession = factory.openSession(true);
7. mapper 映射文件
我们之前定义完 Dao 接口之后,还要定义一个它的实现类 DaoImpl。 而 Mybatis 框架根据 mapper.xml 文件帮助我们自动为接口生成了一个代理对象。
// 使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
然后我们就可以调用接口中的方法了,所以这个 mapper 文件很重要。
1.约定格式
① DAO 的全类名
必须和对应的 mapper 文件中的namespace
一致。
② DAO 中的方法名称必须和 mapper 文件中增删改查操作的id
一致。
③ DAO 中的方法的返回值
必须和 mapper 文件中操作的返回值类型一致。
④ DAO 中的方法的参数类型
必须和 mapper 文件中的输入参数的类型一致。
注: mapper 文件中的 sql 语句不要加分号,id 不能重复。
2.封装输出结果
① resultType
执行 sql 之后得到 ResultSet 转换后的结果类型,使用类型的完全限定名或别名。如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
简单类型
接口方法
String getName(int id);
mapper 文件:
<select id="getName" resultType="java.lang.String">
select name from user where id = #{id}
</select>
对象类型
接口方法
User getUser(int id);
mapper 文件:
<select id="getUser" resultType="user">
select * from user where id = #{id}
</select>
Map 类型
接口方法
@MapKey("id")
Map<String,Object> getUser();
mapper 文件:
<select id="getUser" resultType="java.util.Map">
select * from user
</select>
② resultMap
当数据库表中的列名和实体类的属性名不一致的时候,我们可以使用 resultMap 定义 sql 的结果和 java 对象属性的映射关系。
例如:
<resultMap id="userMap" type="com.xxl.model.User">
<id column="s_id" property="id"/>
<result column="s_name" property="name"/>
<result column="s_age" property="age"/>
<result column="s_sex" property="sex"/>
</resultMap>
<select id="getAll" resultMap="userMap">
select s_id,s_name,s_age,s_sex from user
</select>
3.模糊查询
方式一:{} 中的值必须是 value
<select id="findByName" resultType="user">
select * from user where name like '%${value}%'
</select>
方式二
<select id="findByName" resultType="user">
select * from user where name like "%"#{name}"%"
</select>
4.添加/修改处理 null 值
Mybatis 在执行添加、修改操作时不允许出现空值,所以在添加修改 时,要想使某个列的值为空,必须添加 jdbcType 属性。
例如:
<insert id="add" >
insert into user(name,sex,age) values(#{name,jdbcType=VARCHAR},#{sex,jdbcType=INTEGER},#{age,jdbcType=INTEGER})
</insert>
8. MyBatis 传参
在 Mapper 文件中,我们可以使用 #{param} 和 ${param} 获取接口中传递的参数。
#{param} 是使用占位符的方式,${param} 是采用拼接 sql 语句的方式。
使用 $ 有 sql 注入的风险,所以下面的例子中都会采用 #{param} 的方式。
一个参数
#{}:可以以任意的名字获取参数值。
例如:
<select id="getUser">
select * from user where id = #{uId}
</select>
<delete id="delete">
delete from user where id = #{user_id}
</delete>
多个参数
1.传递多个参数
传输多个参数,Mybatis会将这些参数放到map集合里面,可以通过 @Param 指定mapper 文件中参数的名字。例如:
接口:
List<Student> getUser(@Param("userName") String name,@Param("userSex") int age);
mapper 文件:
<select id="getUser" resultType="user">
select id,name,sex,age from user where name = #{userName} and age = #{userSex}
</select>
2.传递对象
传输参数为 JavaBean,#{} 可以通过属性名获取属性值。
接口:
// 修改用户
boolean update(User user);
mapper 文件:
<update id="update">
update user set name = #{name}, sex = #{sex},age = #{age} where id = #{id}
</update>
3.传递 Map
传递的参数为 map 时,mapper 文件使用 # { key } 获取参数值。
例如:
Map<String,Object> data = new HashMap<String,Object>();
data.put("userName","知否君");
data.put("userAge",21);
接口:
List<User> getUser(Map<String,Object> map);
mapper 文件:
<select id="getUser" resultType="user">
select * from user where name = #{userName} and age = #{userAge}
</select>
9. 动态 SQL
动态 SQL 是 MyBatis 强大特性之一,主要用于解决查询条件不确定
的情况,它可以极大的简化我们拼装 SQL 的操作。
1.if 标签
If 标签用于完成简单的判断,当标签中 test 的值为 true 时,会将其包含的 SQL 片段拼接到其所在的 SQL 语句中。
语法格式:
<if test="条件"> sql 片段 </if>
例如:
<select id="getUser" resultType="user">
select id,name,age,sex from user
where 1=1
<if test="name != null and name !='' ">
and name = #{name}
</if>
</select>
2.where 标签
上面的例子中如果 name 参数存在,为了保证 sql 语句的正确性,我们不得不添加 where 1=1
。
Where 标签就是为了解决 sql 语句中 where 关键字以及条件中第一个 and 或者 or 的问题。
例如:
<select id="getUser" resultType="user">
select id,name,age,sex from user
<where>
<if test="name != null and name !='' ">
and name = #{name}
</if>
</where>
</select>
3.trim 标签
trim 标签可以在条件判断完的 sql 语句前后添加或者去掉指定的字符。
- prefix: 添加前缀
- prefixOverrides: 去掉前缀
- suffix: 添加后缀
- suffixOverrides: 去掉后缀
例如:
<select id="getUser" resultType="user">
select id,name,age,sex from user
<trim prefix="where" suffixOverrides="and">
<if test="name != null and name !='' ">
name = #{name} and
</if>
</trim>
</select>
4.choose(when、otherwise) 标签
choose 标签主要是用于分支判断,类似于 java 中的 switch case,只会满足所有分支中的一个。
例如:
<select id="getUser" resultType="user">
select id,name,age,sex from user
<where>
<choose>
<when test="id != null and id !='' ">
id = #{id}
</when>
<when test="name != null and name !='' ">
name = #{name}
</when>
<otherwise>
sex = '男'
</otherwise>
</choose>
</where>
</select>
5.set 标签
set 标签主要是用于解决修改操作中 sql 语句中可能多出逗号的问题。
例如:
<update id="update">
update user
<set>
<if test="name != null and name !='' ">
name = #{name},
</if>
<if test="sex != null and sex !='' ">
sex = #{sex},
</if>
<if test="age != null and age !='' ">
age = #{age}
</if>
</set>
where id = #{id}
</update>
6.foreach 标签
foreach 标签主要用于循环迭代。
- collection: 要迭代的集合
- item: 当前从集合中迭代出的元素
- open: 开始字符
- close:结束字符
- separator: 元素与元素之间的分隔符
- 迭代的是List集合: index表示的当前元素的下标
- 迭代的Map集合: index表示的当前元素的key
例如:
<select id="getUserList" resultType="user">
select id,name,age,sex from user where id in
<foreach collection="ids" item="userId" open="(" close=")" separator="," >
#{userId}
</foreach>
</select>
7.sql 标签
sql 标签是用于抽取可重用的 sql 片段,将相同的、使用频繁的 sql 片段抽取出来,单独定义,方便多次引用。
例如:
<sql id="BaseSql">
id,name,age,sex
</sql>
<select id="getUserList" resultType="user">
select
<include refid="BaseSql" />
from user
</select>
10. 注解式开发
通过观察 Mapper 文件我们发现,Mapper 文件的核心就是与 Dao 做接口映射,然后里面写一些 sql 语句。
那我能不能不要 mapper 文件,直接在接口里面写 sql 语句?可以的,MyBatsi 为我们提供了注解式开发。
1.修改主配置文件
虽然不用写 mapper 文件了,但是我们要在接口里面写 sql 语句,你得让 MyBatis 知道吧。所以需要告诉 MyBatis 带有注解的接口在哪里。
<!-- 指定带有注解的 dao 接口所在的位置 -->
<mappers>
<package name="com.xxl.dao"/>
</mappers>
- 注解式开发
public interface UserDao {
// 获取所有用户信息
@Select("select * from user")
List<User> getUser();
// 新增用户
@Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age})")
boolean add(User user);
// 修改用户
@Update("update user set name = #{name}, sex = #{sex},age = #{age} where id = #{id}")
boolean update(User user);
// 删除用户
@Delete("delete from user where id = #{id}")
boolean delete(int id);
}
3.测试
@Test
public void testGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
List<User> userList = userDao.getUser();
userList.forEach(user ->{
System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
执行结果:
11. 关联关系
一对一
例如:一个人对应一张身份证,一张身份证对应一个人。在 MyBatis 的 mapper 文件中使用 association 处理一对一关系。
创建表:
A 表的一条记录,对应 B 表的一条记录,且 A 的主键作为 B 表的外键。这主要看以哪张表为中心。
下面例子中将用户信息表的主键(id)作为用户表的外键(info_id)。
用户表:user
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(20) DEFAULT NULL COMMENT '姓名',
`sex` varchar(1) DEFAULT NULL COMMENT '性别',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`info_id` int(11) DEFAULT NULL COMMENT '用户信息id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`user`(`id`, `name`, `sex`, `age`, `info_id`) VALUES (1, '张三', '男', 18, 1);
INSERT INTO `mybatis_demo`.`user`(`id`, `name`, `sex`, `age`, `info_id`) VALUES (2, '李四', '男', 19, 2);
用户信息表:user_info
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`number` varchar(20) DEFAULT NULL COMMENT '身份证编号',
`address` varchar(50) DEFAULT NULL COMMENT '家庭地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`user_info`(`id`, `number`, `address`) VALUES (1, '411121200302174025', '上海外滩');
INSERT INTO `mybatis_demo`.`user_info`(`id`, `number`, `address`) VALUES (2, '411121200222154554', '北京三里屯');
用户实体类:User
public class User {
private int id;
private String name;
private String sex;
private int age;
// 用户信息属性
private UserInfo userInfo;
public UserInfo getUserInfo() {
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
用户信息类:
public class UserInfo {
private int id;
private String number;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "UserInfo{" +
"id=" + id +
", number='" + number + '\'' +
", address='" + address + '\'' +
'}';
}
}
UserDao:
public interface UserDao {
/**
* 获取所有用户信息,包括身份证信息
* @return
*/
List<User> getAll();
}
UserDao.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="com.xxl.dao.UserDao">
<resultMap type="com.xxl.model.User" id="userMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!--
association: 用来处理一对一关系属性封装
property : 关系属性名
javaType: 关系属性的类型
-->
<association property="userInfo" javaType="com.xxl.model.UserInfo" >
<id column="id" property="id" />
<result column="number" property="number"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="getAll" resultMap="userMap">
select user.id,user.name,user.age,user.sex,
info.id,info.number,info.address
from user
left join user_info info
on user.info_id = info.id
</select>
</mapper>
测试代码:
@Test
public void testUserGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
List<User> userList = userDao.getAll();
userList.forEach(user ->{
System.out.println(user+" "+user.getUserInfo());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
测试结果:
一对多
例如:一个部门对应多个员工,一个员工属于一个部门。在 MyBatis 的 mapper 文件中使用 collection 处理一对多关系。
创建表:
A 表的一条记录,对应 B 表的多条记录,且 A 的主键作为 B 表的外键。
下面例子中将部门表的主键(id)作为员工表的外键(dep_id)。
部门表:department
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '部门名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (1, '研发部');
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (2, '人事部');
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (3, '销售部');
员工表:employee
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '员工姓名',
`age` int(11) DEFAULT NULL COMMENT '员工年龄',
`sex` varchar(1) DEFAULT NULL COMMENT '员工性别',
`dep_id` int(11) DEFAULT NULL COMMENT '部门id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (1, '张无忌', 21, '男', 1);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (2, '周芷若', 19, '女', 2);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (3, '赵敏', 19, '女', 3);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (4, '小昭', 20, '女', 2);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (5, '蛛儿', 19, '女', 1);
部门实体类:
public class Department {
private String id;
private String name;
private List<Employee> employeeList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmployeeList() {
return employeeList;
}
public void setEmployeeList(List<Employee> employeeList) {
this.employeeList = employeeList;
}
@Override
public String toString() {
return "Department{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
员工实体类:
public class Employee {
private int id;
private String name;
private String sex;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
DepartmentDao:
public interface DepartmentDao {
List<Department> getAll();
}
DepartmentDao.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="com.xxl.dao.DepartmentDao">
<resultMap type="com.xxl.model.Department" id="deptMap">
<id column="depId" property="id"/>
<result column="depName" property="name"/>
<!--
collection 用来处理集合类型的属性 ,用来处理一对多关系
property: 关系属性名
javaType: 关系属性类型
ofType : 集合中泛型类型:类的全路径名
-->
<collection property="employeeList" javaType="list" ofType="com.xxl.model.Employee">
<id column="empId" property="id"/>
<result column="empName" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</collection>
</resultMap>
<select id="getAll" resultMap="deptMap">
select d.id depId,d.name depName,e.id empId,e.name empName,e.age,e.sex
from department d
left join employee e
on d.id = e.dep_id
</select>
</mapper>
测试代码:
@Test
public void testDepartmentGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
// 3.执行接口的方法
List<Department> departmentList = departmentDao.getAll();
departmentList.forEach(department ->{
System.out.println("部门:"+department+" 员工:"+department.getEmployeeList());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
测试结果:
多对多
例如:例如一个学生可以有多门课程,一门课程可以属于多个学生。多对多可以理解为是一对多和多对一的组合。要实现多对多,一般都需要有一张中间表(也叫关联表),形成多对多的形式。
创建表:
学生表:student
CREATE TABLE `student` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(20) DEFAULT NULL COMMENT '姓名',
`sex` varchar(1) DEFAULT NULL COMMENT '性别',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (1, '张三', '男', 18);
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (2, '李四', '女', 21);
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (3, '王五', '男', 19);
课程表:course
CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '课程名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (1, 'java开发');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (2, '数据结构');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (3, '大数据开发');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (4, '云原生开发');
学生-课程关联表:student_course
CREATE TABLE `student_course` (
`student_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL,
PRIMARY KEY (`student_id`,`course_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (1, 2);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (1, 3);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (2, 1);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (2, 2);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (3, 3);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (3, 4);
学生实体类:
public class Student {
private int id;
private String name;
private String sex;
private int age;
private List<Course> courseList;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
课程实体类:
public class Course {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Course{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
StudentDao:
public interface StudentDao {
List<Student> getAll();
}
StudentDao.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="com.xxl.dao.StudentDao">
<resultMap type="com.xxl.model.Student" id="studentMap">
<id column="studentId" property="id"/>
<result column="studentName" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!--
collection 用来处理集合类型的属性 ,用来处理一对多关系
property: 关系属性名
javaType: 关系属性类型
ofType : 集合中泛型类型:类的全路径名
-->
<collection property="courseList" javaType="list" ofType="com.xxl.model.Course">
<id column="courseId" property="id"/>
<result column="courseName" property="name"/>
</collection>
</resultMap>
<select id="getAll" resultMap="studentMap">
select s.id studentId,s.name studentName,s.age,s.sex,
c.id courseId,c.name courseName
from student s
left join student_course sc
on s.id = sc.student_id
left join course c
on sc.course_id = c.id
</select>
</mapper>
测试代码:
@Test
public void testStudentGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
StudentDao studentDao = session.getMapper(StudentDao.class);
// 3.执行接口的方法
List<Student> studentList = studentDao.getAll();
studentList.forEach(student ->{
System.out.println("学生:"+student+" 课程:"+student.getCourseList());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
测试结果:
12. 分页插件
我们之前在写分页的时候,不仅要自定义一个 Page 类,还要拼接 sql 语句,最后还要封装数据。真的是”恶心他妈妈给恶心开门——恶心到家了。“
为了解决这个问题,MyBatis 为我们提供了一个通用的分页工具:PageHelper。
使用步骤:
1.引入依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.4</version>
</dependency>
2.修改主配置文件
在 environments 标签之前添加:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
3.准备数据 4.测试代码
@Test
public void testGetAll(){
// 1.获取 SqlSession 对象
SqlSession session = MyBatisUtil.getSqlSession();
// 2.使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 3.执行接口的方法
Page page = PageHelper.startPage(2, 3);
List<User> userList = userDao.getUser();
System.out.println("当前页:"+page.getPageNum());
System.out.println("每页条数:"+page.getPageSize());
System.out.println("总条数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("-------------------------");
userList.forEach(user ->{
System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
});
// 4.关闭 SqlSession
MyBatisUtil.close();
}
5.执行结果