Mybatis笔记总结
1 、简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
特点
- 持久层框架
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO
2、基本使用步骤
2.1、搭建环境
2.1.1、引入mybatis相关jar包
<dependencies>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
2.1.2、确保有对应的数据库表
# 创建mybatis数据库
CREATE DATABASE IF NOT EXISTS `mybatis` ;
# 使用mybatis数据库
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
# 创建user表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增的用户id',
`username` varchar(20) NOT NULL COMMENT '用户名',
`birthday` date DEFAULT NULL COMMENT '注册时间',
`gender` varchar(7) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
# 表中插入若干条数据
insert into `user`(`id`,`username`,`birthday`,`gender`,`address`) values (1,'zero','2020-02-25','男','广州市白云区'),(2,'tom','2020-02-23','男','广州市番禺区'),(3,'zero','2020-02-25','男','广州市海珠区'),(4,'jerry','2020-01-25','男','广州市增城区'),(5,'gogo','2020-03-25','男','广州市白云区'),(6,'ben','2020-02-20','男','广州市番禺区'),(7,'peter','2020-02-23','男','广州市天河区'),(12,'haha','2020-03-01','男','广州市从化区');
2.1.3、编写实体类
package com.zero.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int id; //对应表中的id字段
private String username; //对应表中的username字段
private Date birthday; //对应表中的birthday字段
private String gender; //对应表中的gender字段
private String address; //对应表中的address字段
}
注意:一般要求类的属性名与表的字段名一致,映射时才能一一对应
2.1.4、编写实体类对应的dao层接口
package com.zero.dao;
import com.zero.pojo.User;
import java.util.List;
/**
* User实体类的dao接口(统一命名为...Mapper)
*/
public interface UserMapper {
/**
* 获得表中所有的user信息
* @return user列表
*/
List<User> getAllUser();
}
2.2、编写mabatis的核心配置文件
<?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">
<!-- 环境标签,用于配置一个环境,id为环境标识 -->
<environment id="development">
<!-- 配置数据库的事务管理器的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源,POOLED表示采用数据库连接池的方式 -->
<dataSource type="POOLED">
<!-- 配置数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 配置数据库连接的url -->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC"/>
<!-- 配置数据库的连接名 -->
<property name="username" value="root"/>
<!-- 配置数据库的连接名的密码 -->
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
<mappers>
<!-- 配置UserMapper.xml文件到核心配置文件中 -->
<mapper resource="com/zero/dao/UserMapper.xml"/>
</mappers>
</configuration>
2.3、编写dao层接口的mapper映射文件
<?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用于绑定dao层实体类接口的全限定名 -->
<mapper namespace="com.zero.dao.UserMapper">
<!-- sql标签,id用于绑定接口的方法名,resultType用于绑定返回类型(需要写全限定类名),标签体内部写sql语句 -->
<select id="getAllUser" resultType="com.zero.pojo.User">
select * from USER
</select>
</mapper>
注:当使用maven工程,且mapper映射文件写在包内时,此处有个bug。这是由于配置文件应写在resources文件夹内,在Java内的配置文件导不出来。
解决方案:(两种)
- 将mapper配置文件移到resources下。
- 在pom.xml配置文件中配置文件过滤,以支持包内文件的导出(配置内容如下)
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.4、在核心配置文件中注册mapper映射文件
参照2.2核心配置文件的标签内的写法
<!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
<mappers>
<!-- 配置UserMapper.xml文件到核心配置文件中(注意用/而不是.) -->
<mapper resource="com/zero/dao/UserMapper.xml"/>
</mappers>
2.5、封装mybatis的工具类,以便每次都可以直接获得SqlSession对象
package com.zero.util;
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 java.io.IOException;
import java.io.InputStream;
/**
* Mybatis的工具类,用于获取SqlSession对象,避免多余的重复代码
*/
public class MybatisUtil {
//存放sqlSessionFactory对象
private static SqlSessionFactory sqlSessionFactory = null;
static {
//核心配置文件的路径
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
//获得配置文件流对象
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//通过SqlSessionFactoryBuilder对象获得SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 返回一个SqlSession对象
* @return sqlSession
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.6、测试
package com.zero.dao;
import com.zero.pojo.User;
import com.zero.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void test(){
//通过工具类获得sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//通过sqlSession对象获得dao层接口的实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用dao层接口的方法获得sql执行的结果
List<User> userList = userMapper.getAllUser();
//遍历输出结果
for (User user : userList) {
System.out.println(user);
}
//关闭sqlSession对象
sqlSession.close();
}
}
3、CRUD操作
基于2、中的框架环境实现数据库表的增删改查操作。mybatis实现这些操作仅需三步:
- 在dao层接口中编写接口方法
- 在接口的mapper配置文件中编写对应的sql标签
- 通过获取接口实现类对象使用相应方法即可
注意事项:mybatis对于增删改是需要提交事务的,否则数据库不会更新
注意事项:#{}内的参数取值情况,若参数为基本数据类型,则为方法的参数名(可以使用@Param注解在方法参数前绑定被识别的参数名);若是pojo或者map的参数,则参数为其属性名或者键名
3.1、select
-
接口方法
/** * 通过用户id查询用户 * @param id 用户id * @return 返回查询结果 */ User getUserById(int id);
-
mapper文件的sql标签
<!-- 绑定getUserById()方法,parameterType表示方法参数类型,#{}表示取出参数(类似于占位符) --> <select id="getUserById" parameterType="int" resultType="com.zero.pojo.User"> SELECT * FROM user WHERE id = #{id} </select>
-
使用
@Test public void testGetUserById(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //查询id=1的用户 User user = userMapper.getUserById(1); System.out.println(user); sqlSession.close(); }
3.2、insert
-
接口方法
/** * 增加一个用户 * @param user 用户信息 * @return 返回增加结果 */ int addUser(User user);
-
mapper文件的sql标签
<!-- 绑定addUser()方法,#{}对于基本数据类型则需要使用与方法参数相同的名称,对于pojo则名称与属性名一致 --> <insert id="addUser" parameterType="com.zero.pojo.User"> INSERT INTO user (username, birthday, gender, address) VALUES (#{username}, #{birthday}, #{gender}, #{address}) </insert>
-
使用
@Test public void testAddUser(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建用户对象 User user = new User(); user.setUsername("张三"); user.setBirthday(new Date()); user.setGender("男"); user.setAddress("广州市从化区"); //添加用户 int res = userMapper.addUser(user); //判断是否执行成功 if(res>=0){ System.out.println("添加成功"); } //提交事务 sqlSession.commit(); sqlSession.close(); }
3.3、update
-
接口方法
/** * 更新用户信息 * @param user 用户信息 * @return 返回更新结果 */ int updateUser(User user);
-
mapper文件的sql标签
<!-- 绑定updateUser()方法 --> <update id="updateUser" parameterType="com.zero.pojo.User"> UPDATE user SET username = #{username}, birthday = #{birthday}, gender = #{gender}, address = #{address} WHERE id = #{id} </update>
-
使用
@Test public void testUpdateUser(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //获得一个用户 User user = userMapper.getUserById(1); //修改信息 user.setUsername("kkkk"); //数据库执行更新 int res = userMapper.updateUser(user); //判断是否执行成功 if (res > 0){ System.out.println("更新成功"); } //提交事务 sqlSession.commit(); sqlSession.close(); }
3.4、delete
-
接口方法
/** * 通过用户id删除用户 * @param id 用户id * @return 返回删除结果 */ int deleteUserById(int id);
-
mapper文件的sql标签
<!-- 绑定deleteUserById()方法 --> <delete id="deleteUserById" parameterType="int"> DELETE from user WHERE id = #{id} </delete>
-
使用
@Test public void testDeleteUserById(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //删除用户 int res = userMapper.deleteUserById(18); //判断是否执行成功 if(res>0){ System.out.println("删除成功"); } //提交事务 sqlSession.commit(); sqlSession.close(); }
4、mybatis的工作流程
- 应用程序创建SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder对象读取核心配置文件(mybatis-config.xml)
- SqlSessionFactoryBuilder对象创建一个SqlSessionFactory对象
- SqlSessionFactory对象创建一个SqlSession对象
- SqlSession对象读取xxxMapper.xml中的配置文件
- SqlSession通过读取到的配置文件执行相应sql语句,并结果返回给调用程序
生命周期问题
- SqlSessionFactoryBuilder是使用建造者模式建造一个SqlSessionFactory对象,SqlSessionFactory对象通过工厂模式创建SqlSession对象,最终SqlSession对象执行相应的sql调用就结束其任务。本质上,SqlSessionFactory对象相当于一个数据库连接池,SqlSession对象相当于一个数据库连接。
- 基于上面的分析,将三个对象的生命周期分别做以下处理:
- 由于SqlSessionFactoryBuilder对象只是使用一次,可以将其放在局部变量中去创建SqlSessionFactory对象
- 由于SqlSessionFactory对象是起到数据库连接池的作用,故将其设置为单例,并且其生命周期和整个程序共存亡,以便随时获取SqlSession对象
- 由于SqlSession对象类似于数据库连接对象(且官方中说明其是线程不安全的),因此该对象不应该被共享,应当放置于局部变量中,需要时创建,不需要时需要及时关闭,以免占用太多连接导致程序崩坏。
5、核心配置文件
5.1、properties(属性)
该配置项可用于配置一些属性,或者读取外部配置文件中的属性,以供配置文件使用。其配置方式如下:(此处以改造数据库连接属性为例)
-
书写配置文件
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username=root password=root
-
引入外部配置文件
<?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> <!-- 外部引入的方式,引入resources下的db.properties配置文件 --> <properties resource="db.properties"></properties> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 配置数据库驱动,使用了外部文件中配置的driver属性 --> <property name="driver" value="${driver}"/> <!-- 配置数据库连接的url,使用了外部文件中配置的url属性 --> <property name="url" value="${url}"/> <!-- 配置数据库的连接名,使用了外部文件中配置的username属性 --> <property name="username" value="${username}"/> <!-- 配置数据库的连接名的密码,使用了外部文件中配置的password属性 --> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/zero/dao/UserMapper.xml"/> </mappers> </configuration>
-
使用属性
${属性名}
注:内部也可以配置属性,但其同名属性优先级低于外部文件,配置方式如下:
<properties resource="db.properties">
<property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
</properties>
5.2、settings(设置)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。以下介绍几个常用的配置项,具体参照官网解释:https://mybatis.org/mybatis-3/zh/configuration.html#settings
属性名 | 描述 | 取值 | 默认值 |
---|---|---|---|
cacheEnabled | 是否开启缓存 | true | false | true |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射 | true | false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | … | 未设置 |
使用mybatis的默认日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
5.3、typeAliases(类型别名)
类型别名可用于为 Java 类型设置一个缩写名字,以便在写配置时不用去写完全限定名。使用方式如下:
- 逐一配置别名:
<!-- 给com.zero.pojo.User类起一个别名为user -->
<typeAliases>
<typeAlias type="com.zero.pojo.User" alias="user"/>
</typeAliases>
- 扫描整个包配置别名
<!-- 给com.zero.pojo包下的所有类起别名,其别名为其类名首字母小写 -->
<typeAliases>
<package name="com.zero.pojo" />
</typeAliases>
5.4、environments
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory实例只能选择一种环境。 其常用的方式如下,具体的参照官网:https://mybatis.org/mybatis-3/zh/configuration.html#environments
<?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">
<!-- 环境标签,用于配置一个环境,id为环境标识 -->
<environment id="development">
<!-- 配置数据库的事务管理器的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源,POOLED表示采用数据库连接池的方式 -->
<dataSource type="POOLED">
<!-- 配置数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 配置数据库连接的url -->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC"/>
<!-- 配置数据库的连接名 -->
<property name="username" value="root"/>
<!-- 配置数据库的连接名的密码 -->
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
<mappers>
<!-- 配置UserMapper.xml文件到核心配置文件中 -->
<mapper resource="com/zero/dao/UserMapper.xml"/>
</mappers>
</configuration>
5.5、mappers
该配置用于告诉核心配置文件编写sql语句的mapper配置文件去哪里找。其配置的方式主要有以下几种方式:
-
通过使用相对于类路径的资源引用
<!-- 通过资源路径引用 --> <mapper resource="com/zero/dao/UserMapper.xml"/>
-
使用接口的完全限定类名寻找
<!-- 通过接口的完全限定类名引用 --> <mapper class="com.zero.dao.UserMapper" />
-
通过扫描包寻找
<!-- 将扫描包进行寻找--> <package name="com.zero.dao" />
注意事项:第一种方式若将mapper映射文件放置在Java源码路径下,需要配置maven的静态过滤;第二、第三种方式需要在同一个包内写好接口类和mapper映射文件,且要求两个文件的文件名相同
5.6、其他配置
mybatis还提供了以下的配置项,详细查看官网:
6、ResultMap的使用
6.1、字段映射
在进行结果集映射时,mybatis会在幕后为我们创建一个resultMap
,将pojo类的属性和数据库表的字段名一一对应,不过要求属性名和字段名必须相同。但有时候在定义表字段名和pojo类时,会出现不同的情况,这时就需要进行手动的映射了。比如以下表和pojo类的情况:
数据库表
# 创建user表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增的用户id',
`username` varchar(20) NOT NULL COMMENT '用户名',
`birthday` date DEFAULT NULL COMMENT '注册时间',
`gender` varchar(7) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
pojo类
package com.zero.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int id; //对应表中的id字段
private String user_name; //对应表中的username字段
private Date birthday; //对应表中的birthday字段
private String gender; //对应表中的gender字段
private String address; //对应表中的address字段
}
对比表和pojo类,pojo类中的userName属性和表中的user_name字段并不是一一对应,这在默认的映射中会找不到该映射属性,使得最后的查询结果如下:
User(id=1, user_name=null, birthday=Mon Feb 24 00:00:00 CST 2020, gender=男, address=广州市白云区)
User(id=2, user_name=null, birthday=Sun Feb 23 00:00:00 CST 2020, gender=男, address=广州市番禺区)
使用resultMap进行手动映射(仅需映射不同的属性和字段)
<?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.zero.dao.UserMapper">
<!-- resultMap属性通过id绑定下面定义的resultMap -->
<select id="getAllUser" resultMap="user">
select * from USER
</select>
<!-- 定义一个resultMap -->
<resultMap id="user" type="com.zero.pojo.User">
<result property="user_name" column="username"/>
</resultMap>
</mapper>
输出结果
User(id=1, user_name=kkkk, birthday=Mon Feb 24 00:00:00 CST 2020, gender=男, address=广州市白云区)
User(id=2, user_name=tom, birthday=Sun Feb 23 00:00:00 CST 2020, gender=男, address=广州市番禺区)
6.2、多对一
对于单一的属性,通过简单的属性和字段名映射即可解决从数据库表到pojo类的映射,但对于复杂的属性与复杂的sql语句却很难实现一一映射。比如数据库中放置的仅仅是简单的数据类型,但pojo类中放置的是另一个pojo类,这就很难进行映射,需要手动使用result Map进行映射。
-
构造环境
数据表
# 教师表 CREATE TABLE `teacher` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 # 学生表 CREATE TABLE `student` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `tid` int(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `tid` (`tid`), CONSTRAINT `student_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
pojo类
import lombok.Data; //学生类 @Data public class Student { private int id; private String name; private Teacher teacher; //Teacher类对象 }
import lombok.Data; import java.util.List; //教师类 @Data public class Teacher { private int id; private String name; private List<Student> students; //Student类的集合 }
分析
一个教师对应多个学生,多个学生对应一个老师。从学生的角度看,与教师的关系是多对一;从教师的角度看,与学生的关系是一对多。
-
使用方式
编写dao层接口
import com.zero.pojo.Student; import java.util.List; public interface StudentMapper { List<Student> getStudentList(); }
编写mapper配置文件(使用resultMap中的association标签进行关联)
<?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用于绑定dao层实体类接口的全限定名 --> <mapper namespace="com.zero.dao.StudentMapper"> <!-- 编写对应的sql, 返回类型通过resultMap进行映射 --> <select id="getStudentList" resultMap="student"> SELECT s.id sid, s.name sname, t.id tid, t.name tname FROM student s, teacher t WHERE s.tid = t.id; </select> <!-- id表示该resultMap的标识,type表示该映射返回的实际类型 --> <resultMap id="student" type="com.zero.pojo.Student"> <!-- 普通的数据类型字段映射 --> <result property="id" column="sid"/> <result property="name" column="sname"/> <!-- 关联关系的映射,关联关系即对应一个pojo类属性的映射, property为字段名,javaType为该属性的pojo类型 --> <association property="teacher" javaType="com.zero.pojo.Teacher"> <!-- 这里的映射和上面的差不多,只是这里指的property是该pojo类的属性映射 --> <result property="id" column="tid"/> <result property="name" column="tname"/> </association> </resultMap> </mapper>
测试结果
@Test public void studentTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList = studentMapper.getStudentList(); for (Student student : studentList) { System.out.println(student); } /* * Student(id=1, name=tom, teacher=Teacher(id=1, name=zero, students=null)) * Student(id=2, name=gogo, teacher=Teacher(id=1, name=zero, students=null)) * Student(id=3, name=jerry, teacher=Teacher(id=1, name=zero, students=null)) * Student(id=5, name=张三, teacher=Teacher(id=2, name=haha, students=null)) * Student(id=6, name=李四, teacher=Teacher(id=2, name=haha, students=null)) * */ sqlSession.close(); }
6.3、一对多
和上面一对多的情况类似,只不过是描述的情形反转。此处基于上面的环境,从教师的角度进行一对多的映射使用。
dao层接口
import com.zero.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
//教师dao层接口
public interface TeacherMapper {
Teacher getTeacherById(@Param("tid") int id);
}
mapper配置(使用resultMap中的collection标签进行集合绑定)
<?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用于绑定dao层实体类接口的全限定名 -->
<mapper namespace="com.zero.dao.TeacherMapper">
<select id="getTeacherById" resultMap="teacher" parameterType="_int">
SELECT t.id tid, t.name tname, s.id sid,s.name sname
FROM student s, teacher t
WHERE s.tid = t.id AND t.id = #{tid}
</select>
<resultMap id="teacher" type="com.zero.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 一对多使用collection集合标签,javaType表示返回类型,ofType表示返回类型内的泛型-->
<collection property="students" javaType="ArrayList" ofType="com.zero.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
</mapper>
测试结果
@Test
public void teacherTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = teacherMapper.getTeacherById(1);
System.out.println(teacher);
/*
* Teacher(
* id=1,
* name=zero,
* students=[
* Student(id=1, name=tom, teacher=null),
* Student(id=2, name=gogo, teacher=null),
* Student(id=3, name=jerry, teacher=null)
* ])
* */
sqlSession.close();
}
7、动态sql
动态sql,其存在的意义是为了解决在指定条件下对sql进行按需组织与拼接,其本质就是按照条件进行sql语句的拼接,只是将该工作移到了编写sql的配置文件中,而不是在java代码中进行拼接。
搭建环境
-
数据库表
# 创建blog表,包含4个字段 CREATE TABLE blog( id VARCHAR(50) PRIMARY KEY, title VARCHAR(20), author VARCHAR(20), create_time DATE )ENGINE INNODB DEFAULT CHARSET=utf8 # 插入5条数据 INSERT INTO blog VALUES ('123456', 'java', 'zero', '2020-12-12'), ('123457', 'mysql', 'zero', '2020-12-13'), ('123458', 'java web', 'zero', '2020-12-14'), ('123459', 'mybatis', 'zero', '2020-12-15'), ('123450', 'spring', 'zero', '2020-12-16');
-
编写对应的pojo类
import lombok.Data; import java.util.Date; @Data public class Blog { private String id; private String title; private String author; private Date date; }
-
编写对应的dao层接口
import com.zero.pojo.Blog; import java.util.List; import java.util.Map; public interface BlogMapper { //待写接口方法 }
-
编写对应的配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zero.dao.BlogMapper"> <!-- 待写sql配置 --> </mapper>
常用标签
-
where、if标签
where标签会在包含的内容存在时,在前面的sql后添加where关键字, 并过滤第一个子元素前的and/or。if标签会按照传入参数情况,根据test中的结果是否成立来拼接sql。
-
在dao层接口添加一个方法
/** * 使用if标签的动态查询接口 * @param map if判断的参数,为空则不参与查询的选择 * @return 返回查询结果 */ List<Blog> getBlogByIf(Map map);
-
在配置文件中使用标签写sql语句
<!-- where标签会在包含的内容存在时,在前面的sql后添加where关键字, 并过滤第一个子元素前的and/or --> <!-- if标签会判断test里的内容是否成立,成立则添加对应的sql--> <select id="getBlogByIf" resultType="blog" parameterType="map"> SELECT * FROM mybatis.blog <where> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </where> </select>
-
测试结果
@Test public void getBlogIfTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); /* 只传一个title参数时,打印的sql相关日志如下: ==> Preparing: SELECT * FROM mybatis.blog WHERE title = ? ==> Parameters: java(String) <== Columns: id, title, author, create_time <== Row: 123456, java, zero, 2020-12-12 <== Total: 1 */ map.put("title", "java"); /* 只传一个author参数时,打印的sql相关日志如下: ==> Preparing: SELECT * FROM mybatis.blog WHERE author = ? ==> Parameters: zero(String) <== Columns: id, title, author, create_time <== Row: 123450, spring, zero, 2020-12-16 <== Row: 123456, java, zero, 2020-12-12 <== Row: 123457, mysql, zero, 2020-12-13 <== Row: 123458, java web, zero, 2020-12-14 <== Row: 123459, mybatis, zero, 2020-12-15 <== Total: 5 */ map.put("author", "zero"); /* 传两个参数时,答应的sql相关日志如下: ==> Preparing: SELECT * FROM mybatis.blog WHERE title = ? and author = ? ==> Parameters: java(String), zero(String) <== Columns: id, title, author, create_time <== Row: 123456, java, zero, 2020-12-12 <== Total: 1 */ mapper.getBlogByIf(map); sqlSession.close(); }
-
-
choose、when、otherwise标签
这三个标签是一体的,相当于Java中的switch的功能,choose是外层标签,when是判断标签(类似于if),otherwise是没有成立的when标签时的默认选项。choose中的情况只能有一个成立,从上到下选择第一个成立的项。
-
在dao层接口添加一个方法
/** * 使用choose标签的动态查询接口 * @param map choose判断的参数 * @return 返回查询结果 */ Blog getBlogByChoose(Map map);
-
在配置文件中使用标签写sql语句
<!-- 使用choose, when, otherwise标签 --> <select id="getBlogByChoose" parameterType="map" resultType="blog"> SELECT * from mybatis.blog <where> <choose> <when test="title != null"> title = #{title} </when> <when test="author != null"> author = #{author} </when> <otherwise> id = #{id} </otherwise> </choose> </where> </select>
-
测试结果
@Test public void getBlogChooseTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); map.put("title", "java"); map.put("author", "zero"); map.put("id", "123450"); /* 当三个参数都设置时,打印的sql日志如下:仅title参数有用 ==> Preparing: SELECT * from mybatis.blog WHERE title = ? ==> Parameters: java(String) <== Columns: id, title, author, create_time <== Row: 123456, java, zero, 2020-12-12 <== Total: 1 Blog(id=123456, title=java, author=zero, date=null) */ Blog blog = mapper.getBlogByChoose(map); System.out.println(blog); sqlSession.close(); }
-
-
foreach标签
foreach标签用于从集合中遍历出每一个集合元素来拼接sql语句,常用于组合sql中的
in
关键字后集合,其使用如下:-
在dao层接口添加一个方法
/** * 使用foreach标签的动态查询接口 * @param map foreach遍历的参数map * @return 返回查询结果 */ List<Blog> getBlogByForeach(Map map);
-
在配置文件中使用标签写sql语句
<!-- 使用foreach标签,collection指参数中集合,item为每次遍历到的元素名称,open表示在遍历的结果之前添加的符号,close表示在遍历的结果之后添加的符号,separator表示遍历的每个元素之间的分隔符 --> <select id="getBlogByForeach" parameterType="map" resultType="blog"> select * FROM mybatis.blog <where> id IN <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </where> </select>
-
测试结果
@Test public void getBlogForeachTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, List<String>> map = new HashMap<String, List<String>>(); //添加集合信息 ArrayList<String> ids = new ArrayList<String>(); ids.add("123456"); ids.add("123457"); ids.add("123458"); //将集合放置到map中,map的健和配置文件中foreach中的集合参数一致 map.put("ids", ids); /* 打印的sql日志: ==> Preparing: select * FROM mybatis.blog WHERE id IN ( ? , ? , ? ) ==> Parameters: 123456(String), 123457(String), 123458(String) <== Columns: id, title, author, create_time <== Row: 123456, java, zero, 2020-12-12 <== Row: 123457, mysql, zero, 2020-12-13 <== Row: 123458, java web, zero, 2020-12-14 <== Total: 3 Blog(id=123456, title=java, author=zero, date=null) Blog(id=123457, title=mysql, author=zero, date=null) Blog(id=123458, title=java web, author=zero, date=null) */ List<Blog> blogs = mapper.getBlogByForeach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); }
-
8、缓存
mybatis的缓存,指将之前查询到的数据存放在内存中,下一次查询同样的数据时,就不再需要去数据库中查找,而是直接在内存中查找即可。其缓存等级分为一级和二级。
8.1、一级缓存
基于sqlSession连接的缓存,当sqlSession对象还没关闭时,缓存存在;当sqlSession对象关闭时消失。一级缓存是默认存在的,不需要进行设置。
测试验证,基于上面的Blog类和blog表进行测试
-
增加dao层接口方法
/** * 根据id查找博客 * @param id 查找的id * @return 返回查询结果 */ Blog getBlogById(@Param("id") String id);
-
配置sql查询语句
<!-- 根据id查找博客 --> <select id="getBlogById" resultType="blog" parameterType="string"> select * from mybatis.blog WHERE id = #{id} </select>
测试1:测试同一个sqlSession对象的情况
@Test
public void getBlogByIdTest(){
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
//获得SqlSession对象
BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
//使用该对象进行一次查询
Blog blog1 = mapper1.getBlogById("123456");
System.out.println("============================");
//使用该对象进行第二次查询
Blog blog2 = mapper1.getBlogById("123456");
//判断查询结果
System.out.println("blog1 == blog2 :" + (blog1 == blog2));
sqlSession1.close();
}
日志结果:
Opening JDBC Connection
Created connection 1205406622.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
==> Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<== Columns: id, title, author, create_time
<== Row: 123456, java, zero, 2020-12-12
<== Total: 1
============================
blog1 == blog2 :true
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Returned connection 1205406622 to pool.
分析:
只进行了一次sql查询,且两次的的查询结果对象相等,说明Sqlsession对象是有缓存的。
测试2:测试不同的sqlSession对象的情况
@Test
public void getBlogByIdTest(){
//获得SqlSession对象
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
//使用不同的sqlSession对象获得实现类对象
BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
//使用mapper1对象进行一次查询
Blog blog1 = mapper1.getBlogById("123456");
System.out.println("============================");
//使用mapper2对象进行第二次查询
Blog blog2 = mapper2.getBlogById("123456");
//判断查询结果
System.out.println("blog1 == blog2 :" + (blog1 == blog2));
sqlSession1.close();
sqlSession2.close();
}
日志结果:
Opening JDBC Connection
Created connection 1205406622.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
==> Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<== Columns: id, title, author, create_time
<== Row: 123456, java, zero, 2020-12-12
<== Total: 1
============================
Opening JDBC Connection
Created connection 904861801.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
==> Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<== Columns: id, title, author, create_time
<== Row: 123456, java, zero, 2020-12-12
<== Total: 1
blog1 == blog2 :false
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Returned connection 1205406622 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
Returned connection 904861801 to pool.
分析:
进行了两次sql查询,且两次的结果对象不同,说明一级查询仅存在于SqlSession对象中。
影响缓存的刷新的因素
-
手动刷新
sqlSession对象.clearCache();
-
执行update、insert、delete操作
-
关闭SqlSession对象
8.2、二级缓存
一级缓存仅存在于同一个SqlSession中,对于不同的SqlSession是不能共享的,这对于非线程安全的SqlSession来说意义不是很大(因为每次用完都应该关闭,生命周期应该在局部变量中)。这样子就诞生了二级缓存。二级缓存是基于整个mapper配置的namespace作用域的。
工作原理
一个Mapper会有自己的多个SqlSession对象,SqlSeesion对象查询数据时,会自动将数据存到自己的一级缓存,以便下次查找时避免去数据库查找,当多个SqlSession对象并不共享彼此的一级缓存。当开启二级缓存时,当SqlSession关闭时,会将一级缓存的数据放到二级缓存中,这样所有的SqlSession就能共享基于Mapper的数据。
使用方式
-
核心配置文件启动缓存设置
<settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled " value="true"/> </settings>
-
在mapper配置文件中开启缓存
<!-- eviction表示缓存策略,flushInterval表示刷新周期,size表示缓存数量,readOnly表示只读 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
注意:SqlSession未关闭时,不会将数据放置到二级缓存
数据的读取顺序
- 先从二级缓存看是否有数据,有则取出,没有进行下一步
- 再从一级缓存看是否有数据,有则取出,没有进行下一步
- 执行sql,从数据库中获得数据并返回,顺便存到一级缓存
新手上路,恐有错漏,望客官看看即可,还得参照各路大神博文,以免被我误入歧途。