MyBatis 基础知识
MyBatis
基本介绍
ORM(Object Relational Mapping): 对象关系映射,指的是持久化数据和实体对象的映射模式,解决面向对象与关系型数据库存在的互不匹配的现象

MyBatis:
-
MyBatis 是一个优秀的基于 Java 的持久层框架,它内部封装了 JDBC,使开发者只需关注 SQL 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 Statement 等过程。
-
MyBatis 通过 XML 或注解的方式将要执行的各种 Statement 配置起来,并通过 Java 对象和 Statement 中 SQL 的动态参数进行映射生成最终执行的 SQL 语句。
-
MyBatis 框架执行 SQL 并将结果映射为 Java 对象并返回。采用 ORM 思想解决了实体和数据库映射的问题,对 JDBC 进行了封装,屏蔽了 JDBC 底层 API 的调用细节,使我们不用操作 JDBC API,就可以完成对数据库的持久化操作。
MyBatis 官网地址:http://www.mybatis.org/mybatis-3/
参考视频:https://space.bilibili.com/37974444/
基本操作
相关API
Resources:加载资源的工具类
InputStream getResourceAsStream(String fileName):通过类加载器返回指定资源的字节流- 参数 fileName 是放在 src 的核心配置文件名:MyBatisConfig.xml
SqlSessionFactoryBuilder:构建器,用来获取 SqlSessionFactory 工厂对象
SqlSessionFactory build(InputStream is):通过指定资源的字节输入流获取 SqlSession 工厂对象
SqlSessionFactory:获取 SqlSession 构建者对象的工厂接口
SqlSession openSession():获取 SqlSession 构建者对象,并开启手动提交事务SqlSession openSession(boolean):获取 SqlSession 构建者对象,参数为 true 开启自动提交事务
SqlSession:构建者对象接口,用于执行 SQL、管理事务、接口代理
- SqlSession 代表和数据库的一次会话,用完必须关闭
- SqlSession 和 Connection 一样都是非线程安全,每次使用都应该去获取新的对象
注:update 数据需要提交事务,或开启默认提交
SqlSession 常用 API:
| 方法 | 说明 |
|---|---|
| List |
执行查询语句,返回List集合 |
| T selectOne(String statement,Object parameter) | 执行查询语句,返回一个结果对象 |
| int insert(String statement,Object parameter) | 执行新增语句,返回影响行数 |
| int update(String statement,Object parameter) | 执行删除语句,返回影响行数 |
| int delete(String statement,Object parameter) | 执行修改语句,返回影响行数 |
| void commit() | 提交事务 |
| void rollback() | 回滚事务 |
| T getMapper(Class |
获取指定接口的代理实现类对象 |
| void close() | 释放资源 |
映射配置
映射配置文件包含了数据和对象之间的映射关系以及要执行的 SQL 语句,放在 src 目录下
命名:StudentMapper.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:属性,名称空间
-
功能标签:
- < select >:查询功能标签
:新增功能标签 :修改功能标签 :删除功能标签 - id:属性,唯一标识,配合名称空间使用
- resultType:指定结果映射对象类型,和对应的方法的返回值类型(全限定名)保持一致,但是如果返回值是 List 则和其泛型保持一致
- parameterType:指定参数映射对象类型,必须和对应的方法的参数类型(全限定名)保持一致
- statementType:可选 STATEMENT,PREPARED 或 CALLABLE,默认值:PREPARED
- STATEMENT:直接操作 SQL,使用 Statement 不进行预编译,获取数据:$
- PREPARED:预处理参数,使用 PreparedStatement 进行预编译,获取数据:#
- CALLABLE:执行存储过程,CallableStatement
-
参数获取方式:
-
SQL 获取参数:
#{属性名}<mapper namespace="StudentMapper"> <select id="selectById" resultType="student" parameterType="int"> SELECT * FROM student WHERE id = #{id} </select> <mapper/>
-
强烈推荐官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
核心配置
核心配置文件包含了 MyBatis 最核心的设置和属性信息,如数据库的连接、事务、连接池信息等
命名:MyBatisConfig.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"> -
根标签:
:核心根标签
-
引入连接配置文件:
-
: 引入数据库连接配置文件标签 - resource:属性,指定配置文件名
<properties resource="jdbc.properties"/>
-
-
调整设置
:可以改变 Mybatis 运行时行为
-
起别名:
-
:为全类名起别名的父标签 -
:为全类名起别名的子标签 - type:指定全类名
- alias:指定别名
-
:为指定包下所有类起别名的子标签,别名就是类名,首字母小写
<!--起别名--> <typeAliases> <typeAlias type="bean.Student" alias="student"/> <package name="com.seazean.bean"/> <!--二选一--> </typeAliase> -
-
自带别名:
别名 数据类型 string java.lang.String long java.lang.Lang int java.lang.Integer double java.lang.Double boolean java.lang.Boolean .... ......
-
-
配置环境,可以配置多个标签
:配置数据库环境标签,default 属性指定哪个 environment :配置数据库环境子标签,id 属性是唯一标识,与 default 对应 :事务管理标签,type 属性默认 JDBC 事务 :数据源标签 - type 属性:POOLED 使用连接池(MyBatis 内置),UNPOOLED 不使用连接池
:数据库连接信息标签。 - name 属性取值:driver,url,username,password
- value 属性取值:与 name 对应
-
引入映射配置文件
:引入映射配置文件标签 :引入映射配置文件子标签 - resource:属性指定映射配置文件的名称
- url:引用网路路径或者磁盘路径下的 sql 映射文件
- class:指定映射配置类
:批量注册
参考官方文档:https://mybatis.org/mybatis-3/zh/configuration.html
#{}和${}
#{}:占位符,传入的内容会作为字符串加上引号,以预编译的方式传入,将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 的 set 方法来赋值,有效的防止 SQL 注入,提高系统安全性
${}:拼接符,传入的内容会直接替换拼接,不会加上引号,可能存在 sql 注入的安全隐患
-
能用 #{} 的地方就用 #{},不用或少用 ${}
-
必须使用 ${} 的情况:
- 表名作参数时,如:
SELECT * FROM ${tableName} - order by 时,如:
SELECT * FROM t_user ORDER BY ${columnName}
- 表名作参数时,如:
-
sql 语句使用 #{},properties 文件内容获取使用 ${}
日志文件
在日常开发过程中,排查问题时需要输出 MyBatis 真正执行的 SQL 语句、参数、结果等信息,就可以借助 log4j 的功能来实现执行信息的输出。
-
在核心配置文件根标签内配置 log4j
<!--配置LOG4J--> <settings> <setting name="logImpl" value="log4j"/> </settings> -
在 src 目录下创建 log4j.properties
# Global logging configuration log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n #输出到日志文件 #log4j.appender.file=org.apache.log4j.FileAppender #log4j.appender.file.File=../logs/iask.log #log4j.appender.file.layout=org.apache.log4j.PatternLayout #log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n -
pom.xml
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency>
代码实现
-
实体类
public class Student { private Integer id; private String name; private Integer age; ..... } -
StudentMapper
public interface StudentMapper { //查询全部 public abstract List<Student> selectAll(); //根据id查询 public abstract Student selectById(Integer id); //新增数据 public abstract Integer insert(Student stu); //修改数据 public abstract Integer update(Student stu); //删除数据 public abstract Integer delete(Integer id); } -
config.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://192.168.2.184:3306/db1 username=root password=123456 -
MyBatisConfig.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> <!--引入数据库连接的配置文件--> <properties resource="jdbc.properties"/> <!--配置LOG4J--> <settings> <setting name="logImpl" value="log4j"/> </settings> <!--起别名--> <typeAliases> <typeAlias type="bean.Student" alias="student"/> <!--<package name="bean"/>--> </typeAliases> <!--配置数据库环境,可以多个环境,default指定哪个--> <environments default="mysql"> <!--id属性唯一标识--> <environment id="mysql"> <!--事务管理,type属性,默认JDBC事务--> <transactionManager type="JDBC"></transactionManager> <!--数据源信息 type属性连接池--> <dataSource type="POOLED"> <!--property获取数据库连接的配置信息--> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--引入映射配置文件--> <mappers> <!--mapper引入指定的映射配置 resource属性执行的映射配置文件的名称--> <mapper resource="StudentMapper.xml"/> </mappers> </configuration> -
StudentMapper.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="StudentMapper"> <select id="selectAll" resultType="student"> SELECT * FROM student </select> <select id="selectById" resultType="student" parameterType="int"> SELECT * FROM student WHERE id = #{id} </select> <insert id="insert" parameterType="student"> INSERT INTO student VALUES (#{id},#{name},#{age}) </insert> <update id="update" parameterType="student"> UPDATE student SET name = #{name}, age = #{age} WHERE id = #{id} </update> <delete id="delete" parameterType="student"> DELETE FROM student WHERE id = #{id} </delete> </mapper> -
控制层测试代码:根据 id 查询
@Test public void selectById() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = ssf.openSession(); //4.执行映射配置文件中的sql语句,并接收结果 Student stu = sqlSession.selectOne("StudentMapper.selectById", 3); //5.处理结果 System.out.println(stu); //6.释放资源 sqlSession.close(); is.close(); } -
控制层测试代码:新增功能
@Test public void insert() throws Exception{ //1.加载核心配置文件 //2.获取SqlSession工厂对象 //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(true); //4.执行映射配置文件中的sql语句,并接收结果 Student stu = new Student(5, "周七", 27); int result = sqlSession.insert("StudentMapper.insert", stu); //5.提交事务 //sqlSession.commit(); //6.处理结果 System.out.println(result); //7.释放资源 sqlSession.close(); is.close(); }
批量操作
三种方式实现批量操作:
-
标签属性:这种方式属于全局批量 <settings> <setting name="defaultExecutorType" value="BATCH"/> </settings>defaultExecutorType:配置默认的执行器
- SIMPLE 就是普通的执行器(默认,每次执行都要重新设置参数)
- REUSE 执行器会重用预处理语句(只预设置一次参数,多次执行)
- BATCH 执行器不仅重用语句还会执行批量更新(只针对修改操作)
-
SqlSession 会话内批量操作:
public void testBatch() throws IOException{ SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); // 可以执行批量操作的sqlSession SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH); long start = System.currentTimeMillis(); try{ EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); for (int i = 0; i < 10000; i++) { mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1")); } openSession.commit(); long end = System.currentTimeMillis(); // 批量:(预编译sql一次==>设置参数===>10000次===>执行1次(类似管道)) // 非批量:(预编译sql=设置参数=执行)==》10000 耗时更多 System.out.println("执行时长:" + (end - start)); }finally{ openSession.close(); } } -
Spring 配置文件方式(applicationContext.xml):
<!--配置一个可以进行批量执行的sqlSession --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/> <constructor-arg name="executorType" value="BATCH"/> </bean>@Autowired private SqlSession sqlSession;
代理开发
代理规则
分层思想:控制层(controller)、业务层(service)、持久层(dao)
调用流程:

传统方式实现 DAO 层,需要写接口和实现类。采用 Mybatis 的代理开发方式实现 DAO 层的开发,只需要编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口定义创建接口的动态代理对象
接口开发方式:
- 定义接口
- 操作数据库,MyBatis 框架根据接口,通过动态代理的方式生成代理对象,负责数据库的操作
Mapper 接口开发需要遵循以下规范:
-
Mapper.xml 文件中的 namespace 与 DAO 层 mapper 接口的全类名相同
-
Mapper.xml 文件中的增删改查标签的id属性和 DAO 层 Mapper 接口方法名相同
-
Mapper.xml 文件中的增删改查标签的 parameterType 属性和 DAO 层 Mapper 接口方法的参数相同
-
Mapper.xml 文件中的增删改查标签的 resultType 属性和 DAO 层 Mapper 接口方法的返回值相同

实现原理
通过动态代理开发模式,只编写一个接口不写实现类,通过 getMapper() 方法最终获取到 MapperProxy 代理对象,而这个代理对象是 MyBatis 使用了 JDK 的动态代理技术生成的
动态代理实现类对象在执行方法时最终调用了 MapperMethod.execute() 方法,这个方法中通过 switch case 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了 MyBatis 最原生的 SqlSession 方式来执行增删改查
-
代码实现:
public Student selectById(Integer id) { Student stu = null; SqlSession sqlSession = null; InputStream is = null; try{ //1.加载核心配置文件 is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory s = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 sqlSession = s.openSession(true); //4.获取StudentMapper接口的实现类对象 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //5.通过实现类对象调用方法,接收结果 stu = mapper.selectById(id); } catch (Exception e) { e.getMessage(); } finally { //6.释放资源 if(sqlSession != null) { sqlSession.close(); } if(is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } //7.返回结果 return stu; }
结果映射
相关标签
- id 属性:唯一标识
- type 属性:实体对象类型
- autoMapping 属性:结果自动映射
-
:配置主键映射关系标签 -
:配置非主键映射关系标签 - column 属性:表中字段名称
- property 属性: 实体对象变量名称
-
:配置被包含单个对象的映射关系标签,嵌套封装结果集(多对一、一对一) - property 属性:被包含对象的变量名,要进行映射的属性名
- javaType 属性:被包含对象的数据类型,要进行映射的属性的类型(Java 中的 Bean 类)
- select 属性:加载复杂类型属性的映射语句的 ID,会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句
-
:配置被包含集合对象的映射关系标签,嵌套封装结果集(一对多、多对多) - property 属性:被包含集合对象的变量名
- ofType 属性:集合中保存的对象数据类型
-
:鉴别器,用来判断某列的值,根据得到某列的不同值做出不同自定义的封装行为
自定义封装规则可以将数据库中比较复杂的数据类型映射为 JavaBean 中的属性
嵌套查询
子查询:
public class Blog {
private int id;
private String msg;
private Author author;
// set + get
}
<resultMap id="blogResult" type="Blog" autoMapping = "true">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
循环引用:通过缓存解决
<resultMap id="blogResult" type="Blog" autoMapping = "true">
<id column="id" property="id"/>
<collection property="comment" ofType="Comment">
<association property="blog" javaType="Blog" resultMap="blogResult"/><!--y-->
</collection>
</resultMap
多表查询
一对一
一对一实现:
-
数据准备
CREATE TABLE person( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), age INT ); INSERT INTO person VALUES (NULL,'张三',23),(NULL,'李四',24),(NULL,'王五',25); CREATE TABLE card( id INT PRIMARY KEY AUTO_INCREMENT, number VARCHAR(30), pid INT, CONSTRAINT cp_fk FOREIGN KEY (pid) REFERENCES person(id) ); INSERT INTO card VALUES (NULL,'12345',1),(NULL,'23456',2),(NULL,'34567',3); -
bean 类
public class Card { private Integer id; //主键id private String number; //身份证号 private Person p; //所属人的对象 ...... } public class Person { private Integer id; //主键id private String name; //人的姓名 private Integer age; //人的年龄 } -
配置文件 OneToOneMapper.xml,MyBatisConfig.xml 需要引入(可以把 bean 包下起别名)
<?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="OneToOneMapper"> <!--配置字段和实体对象属性的映射关系--> <resultMap id="oneToOne" type="card"> <!--column 表中字段名称,property 实体对象变量名称--> <id column="cid" property="id" /> <result column="number" property="number" /> <!-- association:配置被包含对象的映射关系 property:被包含对象的变量名 javaType:被包含对象的数据类型 --> <association property="p" javaType="bean.Person"> <id column="pid" property="id" /> <result column="name" property="name" /> <result column="age" property="age" /> </association> </resultMap> <select id="selectAll" resultMap="oneToOne"> <!--SQL--> SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id </select> </mapper> -
核心配置文件 MyBatisConfig.xml
<!-- mappers引入映射配置文件 --> <mappers> <mapper resource="one_to_one/OneToOneMapper.xml"/> <mapper resource="one_to_many/OneToManyMapper.xml"/> <mapper resource="many_to_many/ManyToManyMapper.xml"/> </mappers> -
测试类
public class Test01 { @Test public void selectAll() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = ssf.openSession(true); //4.获取OneToOneMapper接口的实现类对象 OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class); //5.调用实现类的方法,接收结果 List<Card> list = mapper.selectAll(); //6.处理结果 for (Card c : list) { System.out.println(c); } //7.释放资源 sqlSession.close(); is.close(); } }
一对多
一对多实现:
-
数据准备
CREATE TABLE classes( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) ); INSERT INTO classes VALUES (NULL,'程序一班'),(NULL,'程序二班') CREATE TABLE student( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30), age INT, cid INT, CONSTRAINT cs_fk FOREIGN KEY (cid) REFERENCES classes(id) ); INSERT INTO student VALUES (NULL,'张三',23,1),(NULL,'李四',24,1),(NULL,'王五',25,2); -
bean 类
public class Classes { private Integer id; //主键id private String name; //班级名称 private List<Student> students; //班级中所有学生对象 ........ } public class Student { private Integer id; //主键id private String name; //学生姓名 private Integer age; //学生年龄 } -
映射配置文件
<mapper namespace="OneToManyMapper"> <resultMap id="oneToMany" type="bean.Classes"> <id column="cid" property="id"/> <result column="cname" property="name"/> <!--collection:配置被包含的集合对象映射关系--> <collection property="students" ofType="bean.Student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="sage" property="age"/> </collection> </resultMap> <select id="selectAll" resultMap="oneToMany"> <!--SQL--> SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid </select> </mapper> -
代码实现片段
//4.获取OneToManyMapper接口的实现类对象 OneToManyMapper mapper = sqlSession.getMapper(OneToManyMapper.class); //5.调用实现类的方法,接收结果 List<Classes> classes = mapper.selectAll(); //6.处理结果 for (Classes cls : classes) { System.out.println(cls.getId() + "," + cls.getName()); List<Student> students = cls.getStudents(); for (Student student : students) { System.out.println("\t" + student); } }
多对多
学生课程例子,中间表不需要 bean 实体类
-
数据准备
CREATE TABLE course( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) ); INSERT INTO course VALUES (NULL,'语文'),(NULL,'数学'); CREATE TABLE stu_cr( id INT PRIMARY KEY AUTO_INCREMENT, sid INT, cid INT, CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id), CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id) ); INSERT INTO stu_cr VALUES (NULL,1,1),(NULL,1,2),(NULL,2,1),(NULL,2,2); -
bean类
public class Student { private Integer id; //主键id private String name; //学生姓名 private Integer age; //学生年龄 private List<Course> courses; // 学生所选择的课程集合 } public class Course { private Integer id; //主键id private String name; //课程名称 } -
配置文件
<mapper namespace="ManyToManyMapper"> <resultMap id="manyToMany" type="Bean.Student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="sage" property="age"/> <collection property="courses" ofType="Bean.Course"> <id column="cid" property="id"/> <result column="cname" property="name"/> </collection> </resultMap> <select id="selectAll" resultMap="manyToMany"> <!--SQL--> SELECT sc.sid,s.name sname,s.age sage,sc.cid,c.name cname FROM student s,course c,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id </select> </mapper>
鉴别器
需求:如果查询结果是女性,则把部门信息查询出来,否则不查询 ;如果是男性,把 last_name 这一列的值赋值
<!--
column:指定要判断的列名
javaType:列值对应的java类型
-->
<discriminator javaType="string" column="gender">
<!-- 女生 -->
<!-- resultType不可缺少,也可以使用resutlMap -->
<case value="0" resultType="com.bean.Employee">
<association property="dept"
select="com.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!-- 男生 -->
<case value="1" resultType="com.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
延迟加载
两种加载
立即加载:只要调用方法,马上发起查询
延迟加载:在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载
优点: 先从单表查询,需要时再从关联表去关联查询,提高数据库性能,因为查询单表要比关联查询多张表速度要快,节省资源
坏处:只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
核心配置文件:
| 标签名 | 描述 | 默认值 |
|---|---|---|
| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
false |
| aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods) | false |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
assocation
分布查询:先按照身份 id 查询所属人的 id、然后根据所属人的 id 去查询人的全部信息,这就是分步查询
-
映射配置文件 OneToOneMapper.xml
一对一映射:
- column 属性表示给要调用的其它的 select 标签传入的参数
- select 属性表示调用其它的 select 标签
- fetchType="lazy" 表示延迟加载(局部配置,只有配置了这个的地方才会延迟加载)
<mapper namespace="OneToOneMapper"> <!--配置字段和实体对象属性的映射关系--> <resultMap id="oneToOne" type="card"> <id column="id" property="id" /> <result column="number" property="number" /> <association property="p" javaType="bean.Person" column="pid" select="one_to_one.PersonMapper.findPersonByid" fetchType="lazy"> <!--需要配置新的映射文件--> </association> </resultMap> <select id="selectAll" resultMap="oneToOne"> SELECT * FROM card <!--查询全部,负责根据条件直接全部加载--> </select> </mapper> -
PersonMapper.xml
<mapper namespace="one_to_one.PersonMapper"> <select id="findPersonByid" parameterType="int" resultType="person"> SELECT * FROM person WHERE id=#{pid} </select> </mapper> -
PersonMapper.java
public interface PersonMapper { User findPersonByid(int id); } -
测试文件
public class Test01 { @Test public void selectAll() throws Exception{ InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = ssf.openSession(true); OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class); // 调用实现类的方法,接收结果 List<Card> list = mapper.selectAll(); // 不能遍历,遍历就是相当于使用了该数据,需要加载,不遍历就是没有使用。 // 释放资源 sqlSession.close(); is.close(); } }
collection
同样在一对多关系配置的
-
映射配置文件 OneToManyMapper.xml
一对多映射:
- column 是用于指定使用哪个字段的值作为条件查询
- select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
<mapper namespace="OneToManyMapper"> <resultMap id="oneToMany" type="bean.Classes"> <id column="id" property="id"/> <result column="name" property="name"/> <!--collection:配置被包含的集合对象映射关系--> <collection property="students" ofType="bean.Student" column="id" select="one_to_one.StudentMapper.findStudentByCid"> </collection> </resultMap> <select id="selectAll" resultMap="oneToMany"> SELECT * FROM classes </select> </mapper> -
StudentMapper.xml
<mapper namespace="one_to_one.StudentMapper"> <select id="findPersonByCid" parameterType="int" resultType="student"> SELECT * FROM person WHERE cid=#{id} </select> </mapper>
注解开发
单表操作
注解可以简化开发操作,省略映射配置文件的编写
常用注解:
- @Select(“查询的 SQL 语句”):执行查询操作注解
- @Insert(“插入的 SQL 语句”):执行新增操作注解
- @Update(“修改的 SQL 语句”):执行修改操作注解
- @Delete(“删除的 SQL 语句”):执行删除操作注解
参数注解:
- @Param:当 SQL 语句需要多个(大于1)参数时,用来指定参数的对应规则
核心配置文件配置映射关系:
<mappers>
<package name="使用了注解的Mapper接口所在包"/>
</mappers>
<!--或者-->
<mappers>
<mapper class="包名.Mapper名"></mapper>
</mappers>
基本增删改查:
-
创建 Mapper 接口
package mapper; public interface StudentMapper { //查询全部 @Select("SELECT * FROM student") public abstract List<Student> selectAll(); //新增数据 @Insert("INSERT INTO student VALUES (#{id},#{name},#{age})") public abstract Integer insert(Student student); //修改操作 @Update("UPDATE student SET name=#{name},age=#{age} WHERE id=#{id}") public abstract Integer update(Student student); //删除操作 @Delete("DELETE FROM student WHERE id=#{id}") public abstract Integer delete(Integer id); } -
修改 MyBatis 的核心配置文件
<mappers> <package name="mapper"/> </mappers> -
bean类
public class Student { private Integer id; private String name; private Integer age; } -
测试类
@Test public void selectAll() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = ssf.openSession(true); //4.获取StudentMapper接口的实现类对象 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //5.调用实现类对象中的方法,接收结果 List<Student> list = mapper.selectAll(); //6.处理结果 for (Student student : list) { System.out.println(student); } //7.释放资源 sqlSession.close(); is.close(); }
多表操作
相关注解
实现复杂关系映射之前我们可以在映射文件中通过配置
| 注解 | 说明 |
|---|---|
| @Results | 代替 使用格式:@Results({ @Result(), @Result() })或@Results({ @Result() }) |
| @Result | 代替< id> 和 column:数据库的列名 property:封装类的变量名 one:需要使用 @One 注解(@Result(one = @One)) Many:需要使用 @Many 注解(@Result(many= @Many)) |
| @One(一对一) | 代替 select:指定调用 Mapper 接口中的某个方法 使用格式:@Result(column="", property="", one=@One(select="")) |
| @Many(多对一) | 代替 select:指定调用 Mapper 接口中的某个方法 使用格式:@Result(column="", property="", many=@Many(select="")) |
一对一
身份证对人
-
PersonMapper 接口
public interface PersonMapper { //根据id查询 @Select("SELECT * FROM person WHERE id=#{id}") public abstract Person selectById(Integer id); } -
CardMapper接口
public interface CardMapper { //查询全部 @Select("SELECT * FROM card") @Results({ @Result(column = "id",property = "id"), @Result(column = "number",property = "number"), @Result( property = "p", // 被包含对象的变量名 javaType = Person.class, // 被包含对象的实际数据类型 column = "pid", // 根据查询出的card表中的pid字段来查询person表 /* one、@One 一对一固定写法 select属性:指定调用哪个接口中的哪个方法 */ one = @One(select = "one_to_one.PersonMapper.selectById") ) }) public abstract List<Card> selectAll(); } -
测试类(详细代码参考单表操作)
//1.加载核心配置文件 //2.获取SqlSession工厂对象 //3.通过工厂对象获取SqlSession对象 //4.获取StudentMapper接口的实现类对象 CardMapper mapper = sqlSession.getMapper(CardMapper.class); //5.调用实现类对象中的方法,接收结果 List<Card> list = mapper.selectAll();
一对多
班级和学生
-
StudentMapper接口
public interface StudentMapper { //根据cid查询student表 cid是外键约束列 @Select("SELECT * FROM student WHERE cid=#{cid}") public abstract List<Student> selectByCid(Integer cid); } -
ClassesMapper接口
public interface ClassesMapper { //查询全部 @Select("SELECT * FROM classes") @Results({ @Result(column = "id", property = "id"), @Result(column = "name", property = "name"), @Result( property = "students", //被包含对象的变量名 javaType = List.class, //被包含对象的实际数据类型 column = "id", //根据id字段查询student表 many = @Many(select = "one_to_many.StudentMapper.selectByCid") ) }) public abstract List<Classes> selectAll(); } -
测试类
//4.获取StudentMapper接口的实现类对象 ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class); //5.调用实现类对象中的方法,接收结果 List<Classes> classes = mapper.selectAll();
多对多
学生和课程
-
SQL 查询语句
SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id} -
CourseMapper 接口
public interface CourseMapper { //根据学生id查询所选课程 @Select("SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id}") public abstract List<Course> selectBySid(Integer id); } -
StudentMapper 接口
public interface StudentMapper { //查询全部 @Select("SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id") @Results({ @Result(column = "id",property = "id"), @Result(column = "name",property = "name"), @Result(column = "age",property = "age"), @Result( property = "courses", //被包含对象的变量名 javaType = List.class, //被包含对象的实际数据类型 column = "id", //根据查询出的student表中的id字段查询中间表和课程表 many = @Many(select = "many_to_many.CourseMapper.selectBySid") ) }) public abstract List<Student> selectAll(); } -
测试类
//4.获取StudentMapper接口的实现类对象 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //5.调用实现类对象中的方法,接收结果 List<Student> students = mapper.selectAll();
缓存机制
缓存概述
缓存:缓存就是一块内存空间,保存临时数据
作用:将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取时直接从缓存中获取,可以减少和数据库交互的次数,提升程序的性能
缓存适用:
- 适用于缓存的:经常查询但不经常修改的,数据的正确与否对最终结果影响不大的
- 不适用缓存的:经常改变的数据 , 敏感数据(例如:股市的牌价,银行的汇率,银行卡里面的钱)等等
缓存类别:
- 一级缓存:SqlSession 级别的缓存,又叫本地会话缓存,自带的(不需要配置),一级缓存的生命周期与 SqlSession 一致。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的 SqlSession 之间的缓存数据区域是互相不影响的
- 二级缓存:mapper(namespace)级别的缓存,二级缓存的使用,需要手动开启(需要配置)。多个 SqlSession 去操作同一个 Mapper 的 SQL 可以共用二级缓存,二级缓存是跨 SqlSession 的
开启缓存:配置核心配置文件中
- cacheEnabled:true 表示全局性地开启所有映射器配置文件中已配置的任何缓存,默认 true

参考文章:https://www.cnblogs.com/ysocean/p/7342498.html
一级缓存
一级缓存是 SqlSession 级别的缓存
工作流程:第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息,得到用户信息,将用户信息存储到一级缓存中;第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
一级缓存的失效:
- SqlSession 不同
- SqlSession 相同,查询条件不同时(还未缓存该数据)
- SqlSession 相同,手动清除了一级缓存,调用
sqlSession.clearCache() - SqlSession 相同,执行 commit 操作或者执行插入、更新、删除,清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读
Spring 整合 MyBatis 后,一级缓存作用:
- 未开启事务的情况,每次查询 Spring 都会创建新的 SqlSession,因此一级缓存失效
- 开启事务的情况,Spring 使用 ThreadLocal 获取当前资源绑定同一个 SqlSession,因此此时一级缓存是有效的
测试一级缓存存在
public void testFirstLevelCache(){
//1. 获取sqlSession对象
SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
//2. 通过sqlSession对象获取UserDao接口的代理对象
UserDao userDao1 = sqlSession.getMapper(UserDao.class);
//3. 调用UserDao接口的代理对象的findById方法获取信息
User user1 = userDao1.findById(1);
System.out.println(user1);
//sqlSession.clearCache() 清空缓存
UserDao userDao2 = sqlSession.getMapper(UserDao.class);
User user = userDao.findById(1);
System.out.println(user2);
//4.测试两次结果是否一样
System.out.println(user1 == user2);//true
//5. 提交事务关闭资源
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
二级缓存
基本介绍
二级缓存是 mapper 的缓存,只要是同一个命名空间(namespace)的 SqlSession 就共享二级缓存的内容,并且可以操作二级缓存
作用:作用范围是整个应用,可以跨线程使用,适合缓存一些修改较少的数据
工作流程:一个会话查询数据,这个数据就会被放在当前会话的一级缓存中,如果会话关闭或提交一级缓存中的数据会保存到二级缓存
二级缓存的基本使用:
-
在 MyBatisConfig.xml 文件开启二级缓存,cacheEnabled 默认值为 true,所以这一步可以省略不配置
<!--配置开启二级缓存--> <settings> <setting name="cacheEnabled" value="true"/> </settings> -
配置 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值<mapper namespace="dao.UserDao"> <!--开启user支持二级缓存--> <cache eviction="FIFO" flushInterval="6000" readOnly="" size="1024"/> <cache></cache> <!--则表示所有属性使用默认值--> </mapper>eviction(清除策略):
LRU– 最近最少使用:移除最长时间不被使用的对象,默认FIFO– 先进先出:按对象进入缓存的顺序来移除它们SOFT– 软引用:基于垃圾回收器状态和软引用规则移除对象WEAK– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
flushInterval(刷新间隔):可以设置为任意的正整数, 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新
size(引用数目):缓存存放多少元素,默认值是 1024
readOnly(只读):可以被设置为 true 或 false
- 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,促进了性能提升
- 可读写的缓存会(通过序列化)返回缓存对象的拷贝, 速度上会慢一些,但是更安全,因此默认值是 false
type:指定自定义缓存的全类名,实现 Cache 接口即可
-
要进行二级缓存的类必须实现 java.io.Serializable 接口,可以使用序列化方式来保存对象。
public class User implements Serializable{}
相关属性
-
select 标签的 useCache 属性
映射文件中的
<select>标签中设置useCache="true"代表当前 statement 要使用二级缓存(默认)注意:如果每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存
<select id="findAll" resultType="user" useCache="true"> select * from user </select> -
每个增删改标签都有 flushCache 属性,默认为 true,代表在执行增删改之后就会清除一、二级缓存,保证缓存的一致性;而查询标签默认值为 false,所以查询不会清空缓存
-
localCacheScope:本地缓存作用域,
中的配置项,默认值为 SESSION,当前会话的所有数据保存在会话缓存中,设置为 STATEMENT 禁用一级缓存
源码解析
事务提交二级缓存才生效:DefaultSqlSession 调用 commit() 时会回调 executor.commit()
-
CachingExecutor#query():执行查询方法,查询出的数据会先放入 entriesToAddOnCommit 集合暂存
// 从二缓存中获取数据,获取不到去一级缓存获取 List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { // 回调 BaseExecutor#query list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将数据放入 entriesToAddOnCommit 集合暂存,此时还没放入二级缓存 tcm.putObject(cache, key, list); } -
commit():事务提交,清空一级缓存,放入二级缓存,二级缓存使用 TransactionalCacheManager(tcm)管理
public void commit(boolean required) throws SQLException { // 首先调用 BaseExecutor#commit 方法,【清空一级缓存】 delegate.commit(required); tcm.commit(); } -
TransactionalCacheManager#commit:查询出的数据放入二级缓存
public void commit() { // 获取所有的缓存事务,挨着进行提交 for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } }public void commit() { if (clearOnCommit) { delegate.clear(); } // 将 entriesToAddOnCommit 中的数据放入二级缓存 flushPendingEntries(); // 清空相关集合 reset(); }private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { // 将数据放入二级缓存 delegate.putObject(entry.getKey(), entry.getValue()); } }
增删改操作会清空缓存:
-
update():CachingExecutor 的更新操作
public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); // 回调 BaseExecutor#update 方法,也会清空一级缓存 return delegate.update(ms, parameterObject); } -
flushCacheIfRequired():判断是否需要清空二级缓存
private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); // 判断二级缓存是否存在,然后判断标签的 flushCache 的值,增删改操作的 flushCache 属性默认为 true if (cache != null && ms.isFlushCacheRequired()) { // 清空二级缓存 tcm.clear(cache); } }
自定义
自定义缓存
<cache type="com.domain.something.MyCustomCache"/>
type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
缓存的配置,只需要在缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如在缓存实现上调用一个名为 setCacheFile(String file) 的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
- 可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。
- 可以使用占位符(如
${cache.file}),以便替换成在配置文件属性中定义的值
MyBatis 支持在所有属性设置完毕之后,调用一个初始化方法, 如果想要使用这个特性,可以在自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口
public interface InitializingObject {
void initialize() throws Exception;
}
注意:对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新,在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
构造语句
动态 SQL
基本介绍
动态 SQL 是 MyBatis 强大特性之一,逻辑复杂时,MyBatis 映射配置文件中,SQL 是动态变化的,所以引入动态 SQL 简化拼装 SQL 的操作
DynamicSQL 包含的标签:
- if
- where
- set
- choose (when、otherwise)
- trim
- foreach
各个标签都可以进行灵活嵌套和组合
OGNL:Object Graphic Navigation Language(对象图导航语言),用于对数据进行访问
参考文章:https://www.cnblogs.com/ysocean/p/7289529.html
where
作用:如果标签返回的内容是以 AND 或 OR 开头的,标签内会剔除掉
表结构:

if
基本格式:
<if test=“条件判断”>
查询条件拼接
</if>
我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果username 不同空时还要加入用户名作为条件,这种情况在我们的多条件组合查询中经常会碰到。
-
UserMapper.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="mapper.UserMapper"> <select id="selectCondition" resultType="user" parameterType="user"> SELECT * FROM user <where> <if test="id != null "> id = #{id} </if> <if test="username != null "> AND username = #{username} </if> <if test="sex != null "> AND sex = #{sex} </if> </where> </select> </mapper> -
MyBatisConfig.xml,引入映射配置文件
<mappers> <!--mapper引入指定的映射配置 resource属性执行的映射配置文件的名称--> <mapper resource="UserMapper.xml"/> </mappers> -
DAO 层 Mapper 接口
public interface UserMapper { //多条件查询 public abstract List<User> selectCondition(Student stu); } -
实现类
public class DynamicTest { @Test public void selectCondition() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = ssf.openSession(true); //4.获取StudentMapper接口的实现类对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(2); user.setUsername("李四"); //user.setSex(男); AND 后会自动剔除 //5.调用实现类的方法,接收结果 List<Student> list = mapper.selectCondition(user); //6.处理结果 for (User user : list) { System.out.println(user); } //7.释放资源 sqlSession.close(); is.close(); } }
set
<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parameterType="com.ys.po.User">
UPDATE user u
<set>
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex}
</if>
</set>
WHERE id=#{id}
</update>
- 如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?
- 如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?
choose
假如不想用到所有的查询条件,只要查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
标签:
<select id="selectUserByChoose" resultType="user" parameterType="user">
SELECT * FROM user
<where>
<choose>
<when test="id !='' and id != null">
id=#{id}
</when>
<when test="username !='' and username != null">
AND username=#{username}
</when>
<otherwise>
AND sex=#{sex}
</otherwise>
</choose>
</where>
</select>
有三个条件,id、username、sex,只能选择一个作为查询条件
-
如果 id 不为空,那么查询语句为:select * from user where id=?
-
如果 id 为空,那么看 username 是否为空
- 如果不为空,那么语句为:select * from user where username=?
- 如果 username 为空,那么查询语句为 select * from user where sex=?
trim
trim 标记是一个格式化的标记,可以完成 set 或者是 where 标记的功能,自定义字符串截取
- prefix:给拼串后的整个字符串加一个前缀,trim 标签体中是整个字符串拼串后的结果
- prefixOverrides:去掉整个字符串前面多余的字符
- suffix:给拼串后的整个字符串加一个后缀
- suffixOverrides:去掉整个字符串后面多余的字符
改写 if + where 语句:
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
SELECT * FROM user
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null">
AND username=#{username}
</if>
<if test="sex != null">
AND sex=#{sex}
</if>
</trim>
</select>
改写 if + set 语句:
<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parameterType="com.ys.po.User">
UPDATE user u
<trim prefix="set" suffixOverrides=",">
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex},
</if>
</trim>
WHERE id=#{id}
</update>
foreach
基本格式:
<foreach>:循环遍历标签。适用于多个参数或者的关系。
<foreach collection=“”open=“”close=“”item=“”separator=“”>
获取参数
</foreach>
属性:
- collection:参数容器类型, (list-集合, array-数组)
- open:开始的 SQL 语句
- close:结束的 SQL 语句
- item:参数变量名
- separator:分隔符
需求:循环执行 sql 的拼接操作,SELECT * FROM user WHERE id IN (1,2,5)
-
UserMapper.xml片段
<select id="selectByIds" resultType="user" parameterType="list"> SELECT * FROM student <where> <foreach collection="list" open="id IN(" close=")" item="id" separator=","> #{id} </foreach> </where> </select> -
测试代码片段
//4.获取StudentMapper接口的实现类对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<Integer> ids = new ArrayList<>(); Collections.addAll(list, 1, 2); //5.调用实现类的方法,接收结果 List<User> list = mapper.selectByIds(ids); for (User user : list) { System.out.println(user); }
SQL片段
将一些重复性的 SQL 语句进行抽取,以达到复用的效果
格式:
<sql id=“片段唯一标识”>抽取的SQL语句</sql> <!--抽取标签-->
<include refid=“片段唯一标识”/> <!--引入标签-->
使用:
<sql id="select">SELECT * FROM user</sql>
<select id="selectByIds" resultType="user" parameterType="list">
<include refid="select"/>
<where>
<foreach collection="list" open="id IN(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
逆向工程
MyBatis 逆向工程,可以针对单表自动生成 MyBatis 执行所需要的代码(mapper.java、mapper.xml、pojo…)
generatorConfig.xml
<?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>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatisrelation" userId="root"
password="root">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL和NUMERIC类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置!! -->
<javaModelGenerator targetPackage="com.ys.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置!! -->
<sqlMapGenerator targetPackage="com.ys.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置,重要!! -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.ys.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表,要生成哪些表,就写哪些表,要和数据库中对应,不能写错! -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
</context>
</generatorConfiguration>
生成代码:
public void testGenerator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指向逆向工程配置文件
File configFile = new File(GeneratorTest.class.
getResource("/generatorConfig.xml").getFile());
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
参考文章:https://www.cnblogs.com/ysocean/p/7360409.html
构建 SQL
基础语法
MyBatis 提供了 org.apache.ibatis.jdbc.SQL 功能类,专门用于构建 SQL 语句
| 方法 | 说明 |
|---|---|
| SELECT(String... columns) | 根据字段拼接查询语句 |
| FROM(String... tables) | 根据表名拼接语句 |
| WHERE(String... conditions) | 根据条件拼接语句 |
| INSERT_INTO(String tableName) | 根据表名拼接新增语句 |
| INTO_VALUES(String... values) | 根据值拼接新增语句 |
| UPDATE(String table) | 根据表名拼接修改语句 |
| DELETE_FROM(String table) | 根据表名拼接删除语句 |
增删改查注解:
- @SelectProvider:生成查询用的 SQL 语句
- @InsertProvider:生成新增用的 SQL 语句
- @UpdateProvider:生成修改用的 SQL 语句注解
- @DeleteProvider:生成删除用的 SQL 语句注解。
- type 属性:生成 SQL 语句功能类对象
- method 属性:指定调用方法
基本操作
-
MyBatisConfig.xml 配置
<!-- mappers引入映射配置文件 --> <mappers> <package name="mapper"/> </mappers> -
Mapper 类
public interface StudentMapper { //查询全部 @SelectProvider(type = ReturnSql.class, method = "getSelectAll") public abstract List<Student> selectAll(); //新增数据 @InsertProvider(type = ReturnSql.class, method = "getInsert") public abstract Integer insert(Student student); //修改操作 @UpdateProvider(type = ReturnSql.class, method = "getUpdate") public abstract Integer update(Student student); //删除操作 @DeleteProvider(type = ReturnSql.class, method = "getDelete") public abstract Integer delete(Integer id); } -
ReturnSQL 类
public class ReturnSql { //定义方法,返回查询的sql语句 public String getSelectAll() { return new SQL() { { SELECT("*"); FROM("student"); } }.toString(); } //定义方法,返回新增的sql语句 public String getInsert(Student stu) { return new SQL() { { INSERT_INTO("student"); INTO_VALUES("#{id},#{name},#{age}"); } }.toString(); } //定义方法,返回修改的sql语句 public String getUpdate(Student stu) { return new SQL() { { UPDATE("student"); SET("name=#{name}","age=#{age}"); WHERE("id=#{id}"); } }.toString(); } //定义方法,返回删除的sql语句 public String getDelete(Integer id) { return new SQL() { { DELETE_FROM("student"); WHERE("id=#{id}"); } }.toString(); } } -
功能实现类
public class SqlTest { @Test //查询全部 public void selectAll() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(true); //4.获取StudentMapper接口的实现类对象 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //5.调用实现类对象中的方法,接收结果 List<Student> list = mapper.selectAll(); //6.处理结果 for (Student student : list) { System.out.println(student); } //7.释放资源 sqlSession.close(); is.close(); } @Test //新增 public void insert() throws Exception{ //1 2 3 4获取StudentMapper接口的实现类对象 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //5.调用实现类对象中的方法,接收结果 ->6 7 Student stu = new Student(4,"赵六",26); Integer result = mapper.insert(stu); } @Test //修改 public void update() throws Exception{ //1 2 3 4 5调用实现类对象中的方法,接收结果 ->6 7 Student stu = new Student(4,"赵六wq",36); Integer result = mapper.update(stu); } @Test //删除 public void delete() throws Exception{ //1 2 3 4 5 6 7 Integer result = mapper.delete(4); } }
运行原理
运行机制

MyBatis 运行过程:
-
加载 MyBatis 全局配置文件,通过 XPath 方式解析 XML 配置文件,首先解析核心配置文件,
标签中配置属性项有 defaultExecutorType,用来配置指定 Executor 类型,将配置文件的信息填充到 Configuration对象。最后解析映射器配置的映射文件,并构建 MappedStatement 对象填充至 Configuration,将解析后的映射器添加到 mapperRegistry 中,用于获取代理 -
创建一个 DefaultSqlSession 对象,根据参数创建指定类型的 Executor,二级缓存默认开启,把 Executor 包装成缓存执行器
-
DefaulSqlSession 调用 getMapper(),通过 JDK 动态代理获取 Mapper 接口的代理对象 MapperProxy
-
执行 SQL 语句:
- MapperProxy.invoke() 执行代理方法,通过 MapperMethod#execute 判断执行的是增删改查中的哪个方法
- 查询方法调用 sqlSession.selectOne(),从 Configuration 中获取执行者对象 MappedStatement,然后 Executor 调用 executor.query 开始执行查询方法
- 首先通过 CachingExecutor 去二级缓存查询,查询不到去一级缓存查询,最后去数据库查询并放入一级缓存
- Configuration 对象根据 标签的 statementType 属性,根据属性选择创建哪种对象
- 判断 BoundSql 是否被创建,没有创建会重新封装参数信息到 BoundSql
- StatementHandler 的构造方法中,创建了 ParameterHandler 和 ResultSetHandler 对象
interceptorChain.pluginAll(statementHandler):拦截器链
-
prepareStatement():通过 StatementHandler 创建 JDBC 原生的 Statement 对象getConnection():获取 JDBC 的 Connection 对象handler.prepare():初始化 Statement 对象instantiateStatement(Connection connection):Connection 中的方法实例化对象- 获取普通执行者对象:
Connection.createStatement() - 获取预编译执行者对象:
Connection.prepareStatement()
- 获取普通执行者对象:
handler.parameterize():进行参数的设置ParameterHandler.setParameters():通过 ParameterHandler 设置参数typeHandler.setParameter():底层通过 TypeHandler 实现,回调 JDBC 的接口进行设置
-
StatementHandler.query():调用 JDBC 原生的 PreparedStatement 执行 SQLpublic <E> List<E> query(Statement statement, ResultHandler resultHandler) { // 获取 SQL 语句 String sql = boundSql.getSql(); statement.execute(sql); // 通过 ResultSetHandler 对象封装结果集,映射成 JavaBean return resultSetHandler.handleResultSets(statement); }resultSetHandler.handleResultSets(statement):处理结果集-
handleResultSet(rsw, resultMap, multipleResults, null):底层回调-
handleRowValues():逐行处理数据,根据是否配置了属性选择是否使用简单结果集映射 -
首先判断数据是否被限制行数,然后进行结果集的映射
-
最后将数据存入 ResultHandler 对象,底层就是 List 集合
public class DefaultResultHandler implements ResultHandler<Object> { private final List<Object> list; public void handleResult(ResultContext<?> context) { list.add(context.getResultObject()); } }
-
-
-
return collapseSingleResultList(multipleResults):可能存在多个结果集的情况
-
-
localCache.putObject(key, list):放入一级(本地)缓存 - 每个创建出来的对象不是直接返回的,而是
interceptorChain.pluginAll(parameterHandler) - 获取到所有 Interceptor(插件需要实现的接口),调用
interceptor.plugin(target)返回 target 包装后的对象 - 插件机制可以使用插件为目标对象创建一个代理对象,代理对象可以拦截到四大对象的每一个执行
- 分页可以将很多条结果进行分页显示。如果当前在第一页,则没有上一页。如果当前在最后一页,则没有下一页,需要明确当前是第几页,这一页中显示多少条结果。
- MyBatis 是不带分页功能的,如果想实现分页功能,需要手动编写 LIMIT 语句,不同的数据库实现分页的 SQL 语句也是不同,手写分页 成本较高。
- PageHelper:第三方分页助手,将复杂的分页操作进行封装,从而让分页功能变得非常简单
-
导入 PageHelper 的 Maven 坐标
-
在 MyBatis 核心配置文件中配置 PageHelper 插件
注意:分页助手的插件配置在通用 Mapper 之前
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 指定方言 --> <property name="dialect" value="mysql"/> </plugin> </plugins> <mappers>.........</mappers> -
与 MySQL 分页查询页数计算公式不同
static <E> Page<E> startPage(int pageNum, int pageSize):pageNum第几页,pageSize页面大小@Test public void selectAll() { //第一页:显示2条数据 PageHelper.startPage(1,2); List<Student> students = sqlSession.selectList("StudentMapper.selectAll"); for (Student student : students) { System.out.println(student); } } PageInfo<Student> info = new PageInfo<>(list): list 是 SQL 执行返回的结果集合,参考上一节- startPage():设置分页参数
- PageInfo:分页相关参数功能类。
- getTotal():获取总条数
- getPages():获取总页数
- getPageNum():获取当前页
- getPageSize():获取每页显示条数
- getPrePage():获取上一页
- getNextPage():获取下一页
- isIsFirstPage():获取是否是第一页
- isIsLastPage():获取是否是最后一页
return list.get(0):返回结果集的第一个数据

插件使用
插件原理
实现原理:插件是按照插件配置顺序创建层层包装对象,执行目标方法的之后,按照逆向顺序执行(栈)
在四大对象创建时:
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor{
//intercept:拦截目标对象的目标方法的执行
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询11号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:" + target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:" + value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 11);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
// plugin:包装目标对象的,为目标对象创建一个代理对象
@Override
public Object plugin(Object target) {
//可以借助 Plugin 的 wrap 方法来使用当前 Interceptor 包装我们目标对象
System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象" + target);
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
}
// setProperties:将插件注册时的property属性设置进来
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:" + properties);
}
}
核心配置文件:
<!--plugins:注册插件 -->
<plugins>
<plugin interceptor="mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
分页插件

分页操作
开发步骤:
参数获取
PageInfo构造方法:
PageInfo相关API:

浙公网安备 33010602011771号