Mybatis

小德 2021-12-02 12:57:59
Categories: Tags:

MyBatis

0 为什么使用MyBatis

1
2
开发的本质是: 提高工作效率和代码性能。
MyBatis是非常优秀的持久层ORM框架,简化开发人员对关系数据库的使用。
1
2
3
4
5
6
7
8
9
10
11
12
传统JDBC步骤
传统的JDBC(Java DataBase Connectivy)步骤:

加载数据库驱动
创建并获取数据库链接
创建jdbc statement对象
设置sql语句
设置sql语句中的参数(使用preparedStatement)
通过statement执行sql并获取结果
对sql执行结果进行解析
释放资源,包括resultSet、preparedStatement、connection

​ JDBC 面临的问题

1 数据库连接,使用时就创建,不使用时立即释放,对数据库进行频繁连接开始和关闭,

​ 造成数据库资源浪费,影响数据库的性能

​ 改进: 使用数据库连接池管理 连接

2 将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护

​ 改进: 将sql语句配置到xml配置文件中 , 不用重新编译Java代码

3 占位符位置和设置参数值,硬编码在java代码, 拼接Sql等问题。

​ 改进: 将sql语句及占位符和参数全部配置到xml中。

4 resultSet中遍历结果集数据时 ,映射结果集。 获取字段,重复劳动。

1
2
3
4
5
6

MyBatis则简化了传统JDBC方法,解决了:
①频繁链接和释放数据库的浪费、
②SQL语句与代码的高耦合、
③参数传递麻烦、
④结果解析麻烦。

1 MyBatis入门案例

基本操作

1 创建一个测试表, 创建测试实体类

2 用MyBatis ,导入MyBatis 需要的jar 包,或者Pom 文件,导入核心配置文件

3 测试核心配置文件中是否能得到 SqlSession

4 创建映射文件,创建接口

5 做测试

创建一个maven项目 导入Mybatis的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

在resources 文件夹中导入 核心配置文件和数据库配置文件 mybatis-config.xml database.properties

编写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void count() throws Exception {
SqlSession sqlSession = null;
try {
// 1. 读取MyBatis核心配置文件
String resource = "mybatis-config.xml";
// 获取对mybatis-config.xml的输入流
InputStream is = Resources.getResourceAsStream(resource);
// 2. 使用SqlSessionFactoryBuilder读取配置文件并构建
// SqlSessionFactory实例
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(is);
// 3. 创建SqlSession实例
sqlSession = factory.openSession();
// 4. 创建SysUserMapper接口实例,调用其方法执行相关的SQL语句
int count = sqlSession.getMapper(SysUserMapper.class).count();
logger.debug("SysUserMapperTest count ---> " + count);
} finally{
// 5. 关闭SqlSession
sqlSession.close();
}

}

​ 针对于数据库的表 编写 操作

1
2
3
4
public interface StudentMapper {
int count();
}

编写实现

1
2
3
4
5
6
7
8
9
<?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.lpc.dao.StudentMapper">
<select id="count" resultType="int">
select count(*) from student
</select>
</mapper>

​ 注意事项: 可能测试不成功,拿不到 StudentMapper.xml 映射文件,出现该问题的原因是,Maven打包的时候不打包 src/main/java下面的 xml 文件。

解决方法有2种: 第一个是 在Pom 文件中添加 以下内容,让 maven 打包的时候 打包 .xml 的文件。

1
2
3
4
5
6
7
8
9
10
11
<!--maven 默认不会 加载 .xml文件-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>

第二种办法是 不要把 映射文件 .xml 放入 src/main/java , 而是放入 src/main/resources 下面

如: 建立一个 mapper文件夹,专门放 映射文件

1
2
3
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>

2 针对于纯注解的方式

在注解上 写Sql语句

1
2
3
4
public interface StudentMapper {
@Select("select count(*) from student")
int count();
}

在核心配置文件中打开 映射

1
2
3
4
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper class="com.lpc.dao.StudentMapper"/>
</mappers>

自己完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 根据用户id查询用户信息
* @return
*/
@Select("select * from tblstudent where studentId =#{studentId}")
public Student selectStudentById(Integer studentId);

/**
* 通过学生姓名模糊查询用户信息
* @return
*/
@Select("select * from tblstudent where studentName LIKE concat('%',#{studentName},'%')")
public List<Student> selectStudentByName(String studentName);

/**
* 插入学生信息
* @return
*/
@Insert("insert into tblstudent(studentName,age,studentNo,birthDay)values(#{studentName},#{age},#{studentNo},#{birthDay})")
public int addStudent(Student student);

/**
*更新学生信息
* @return
*/
@Update("update tblstudent set studentName = #{studentName},age = #{age} where studentId = #{studentId} ")
public int updateStudent(Student student);

/**
* 刪除学生信息
* @return
*/
@Delete("delete from tblstudent where studentId =#{studentId}")
public int deleteStudent(Integer id);

3 优化 SqlSessionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.cvs.utils;

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;

public class MyBatisUtil {
private static SqlSessionFactory factory;

static{ // 通过静态代码块,唯一一次创建SqlSessioinFactory实例
try {
InputStream is = Resources.getResourceAsStream(
"mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException("初始化失败", e);
}
}

// 创建并返回SqlSession实例
public static SqlSession createSqlSession(){
return factory.openSession(false); // 关闭自动提交开启事务
}

// 关闭SqlSession
public static void closeSqlSession(SqlSession sqlSession){
if (sqlSession != null)
sqlSession.close();
}
}

4 三个核心对象总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SqlSessionFactoryBuilder的最佳使用范围是什么?
用过即丢,负责构建SqlSessionFactory
推荐作用域范围:局部变量

SqlSessionFactory的最佳使用范围是什么?
生命周期与应用的生命周期相同
最佳作用域范围:应用的全局作用域
用 OpenSession() 创建SqlSession 实例, 默认是 false ,关闭事物提交。
OpenSession(booolean )

SqlSession的最佳使用范围是什么?
线程级
一个request请求期间
两种使用方式:
通过 namespace+id 字符串运行映射的SQL语句
基于Mapper接口方式执行SQL语句

5 做一个针对于 单表的增删改查

添加学生

SqlSession 的事物管理 1 可以直接打开事物 session=sqlSessionFactory.openSession(true);

也可以得到连接对象的时候 去提交 commit session.commit();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void test3(){
// 1 得到指向 核心配置文件的输入流
InputStream inputStream= null;
SqlSession session=null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2 得到SqlSessionfactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
session=sqlSessionFactory.openSession(true);
StudentMapper sm= session.getMapper(StudentMapper.class);
Student student=new Student(null,"司马1","男");
sm.addStudent(student);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(session!=null){
session.close();
}
}
}

6 映射文件 insert

主键的处理方式:

如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:

1
2
3
4
5
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>

也可以 关键字 default

如果是Oracle数据库,可用序列形式

1
2
3
4
5
6
7
8
<insert id="addUser" parameterType="user" >
<!-- keyProperty: 将序列号设置到user对象中,且在controller中可以使用 -->
<selectKey keyProperty="id" order="BEFORE" resultType="string">
SELECT seq_changez_user.nextval id from dual
</selectKey>
insert into temp_changez_user (id, username) values(#{id}, #{username})
</insert>

7 数据库厂商标识

1
2
3
4
5
6
7
8
9
10
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:


<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>

对于不同的数据库 ,可写 两套 Sql,增加扩展性

针对插入, 主键 ,可测试。

1
2
3
4
<insert id="addStudent" parameterType="com.lpc.entity.Student" 
keyProperty="sid" useGeneratedKeys="true" databaseId="mysql">
insert into student(sname,sex,age) values(#{sname},#{sex},#{age})
</insert>
1
2
3
4
5
6
7
<insert id="addUser" parameterType="user" databaseId="oracle">
<selectKey keyProperty="id" order="BEFORE" resultType="_int">
SELECT seq_changez_user.nextval id from dual
</selectKey>
insert into temp_changez_user (id, username) values(#{id}, #{username})
</insert>

8 MyBatis 配置别名

1 每个类 配置 任意想要的名字

1
2
3
4
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="blog" type="domain.blog.Blog"/>
</typeAliases>

2 扫描包的形式

1
2
3
<typeAliases>
<package name="domain.blog"/>
</typeAliases>

MyBatis 使用的别名 在Sql 映射文件中 是不区分大小写的。

会使用 Bean 的首字母小写的非限定类名来作为它的别名

3 给特殊的类 指定别名, 如 不同包下面的相同的实体类

1
2
3
4
@Alias("author")
public class Author {
...
}

​ 此外,对于常见的 Java内置类型,MyBatis框架中 也内建了 相应的别名,一般是

如 String 别名 string Boolean 别名 boolean

基本数据类型别名 byte _byte int _int

9 MyBatis 入参处理

1 单个参数 随意传

1
2
3
4
5
List<Student> findByName(String a);

<select id="findByName" resultType="student">
select * from student where sname like concat('%',#{a},'%')
</select>

2 多个参数 都会被MyBatis重新包装成一个Map传入

1
2
3
4
5
6
7
8
9
List<Student> findByName(String a,Integer age);

<select id="findByName" resultType="student">
select * from student where sname like concat('%',#{arg0},'%') and age>#{arg1}
</select>

<select id="findByName" resultType="student">
select * from student where sname like concat('%',#{param1},'%') and age>#{param2}
</select>

3 命名 参数 为参数使用@Param起一个名字

1
2
3
4
5
List<Student> findByName(@Param("bb") String a,@Param("a") Integer age);

<select id="findByName" resultType="student">
select * from student where sname like concat('%',#{bb},'%') and age>#{a}
</select>

4 实体类 传入 传递POJO

5 封装为 Map参数

1
2
3
4
5
6
7
8
9
Map<String,Object> map=new HashMap<>();
map.put("bb","司马");
map.put("a",10);

List<Student> findTj(Map map);

<select id="findTj" parameterType="map" resultType="student">
select * from student where sname like concat('%',#{bb},'%') and age>#{a}
</select>

10 #{} 和 ${} 的 用法 和区别

Mybatis的Mapper映射文件中,有两种方式可以引用形参变量进行取值: #{} 和 ${}

#{}: 解析为SQL时,会将形参变量的值取出,并自动给其添加引号 预编译的

1
2
3
select * from student where sname =#{name}

真实在执行的 时候 会给 name 加 单引号

${}: 解析为 SQL时,直接将值 放在那里

1
2
3
4
5
select * from student where sname =${name}

select * from student where sname =司马
会报错,要人为的加 ' '
select * from student where sname ='${name}'

两者的区别

1
2
3
4
5
6
7
8
${}方式是将形参和  SQL语句直接拼接形成完整的SQL命令后,再进行编译,所以有Sql注入的问题。

当 username = "' OR 1=1 OR '" 传入后,${}将变量内容直接和SQL语句进行拼接,结果如下:

select * from student where sname =' ' OR 1=1 OR ' '

所以这种会产生 Sql注入的问题。

​ 总结

1
2
3
4
#{}方式则是先用占位符代替参数将SQL语句先进行预编译,然后再将参数中的内容替换进来。由于SQL语句已经被预编译过,其SQL意图将无法通过非法的参数内容实现更改,其参数中的内容,无法变为SQL命令的一部分。
${}方式是将形参和 SQL语句直接拼接形成完整的SQL命令后,再进行编译,所以有Sql注入的问题。

故,#{}可以防止SQL注入而${}却不行

​ # 和 $ 的使用场景

1
2
3
4
5
6
7
8
模糊查询

SELECT * student user where username like '%${value}%'

推荐使用 #{}

SELECT * FROM student WHERE username LIKE CONCAT('%', #{username}, '%')

只能使用 $() 的场景

1
2
3
4
5
6
7
8
9
10
由于#{}会给参数内容自动加上引号,会在有些需要表示字段名、表名的场景下,SQL将无法正常执行。

order by 排序的情况
<select id="order" resultType="student">
select * from student order by #{age} desc
</select>

<select id="order" resultType="student">
select * from student order by ${age} desc
</select>

11 resultMap 的用法

1 当 实体类 和 数据库字段不能映射时, 这类也可以用数据库 表别名 解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Integer id;
private String name;
private String sex;
private Integer age;

<!--id 是 起的 唯一的名字 type 是实体类 类型-->
<resultMap id="ss" type="s1">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</resultMap>


<select id="findAll" resultMap="ss">
select * from student
</select>

注意: 如果此处 漏写其他的 字段,和 Mybatis核心配置文件中的自动映射有关 可查看文档。。

1
2
3
4
5
<!-- 配置mybatis的log实现为LOG4J -->
<settings>
<setting name="logImpl" value="LOG4J" />
<setting name="autoMappingBehavior" value="NONE"/>
</settings>

12 resultMap 一对一 一对多

设计数据库 表 用户表 和 订单表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 用户表
CREATE TABLE `tt`.`users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`uname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
)
-- 订单表
CREATE TABLE `tt`.`orders` (
`oid` int(11) NOT NULL AUTO_INCREMENT,
`oname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`num` int(11) NOT NULL COMMENT '商品数量',
`uid` int(11) NULL DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`oid`) USING BTREE
)

自己造 一点数据 ,分析 用户 和订单的关系 一个用户 对应 多个 订单 一个订单 对应一个用户

一对一: 订单 association

1
2
3
4
5
6
7
8
实体类设计:

public class Orders {
private Integer oid;
private String oname;
private Integer num;
private Integer uid;
private Users users;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<Orders> findAll();

<resultMap id="oo" type="orders">
<id property="oid" column="oid"/>
<!--association 用于一对一的关联 一个订单关联查询出来一个用户 -->
<association property="users" column="users" javaType="com.lpc.entity.Users">
<id property="uid" column="uid"/>
</association>
</resultMap>

<select id="findAll" resultMap="oo">
select o.*,u.*
from orders o, users u
where o.uid=u.uid
</select>

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="rm" type="com.lpc.entity.Orders">
<id property="oid" column="oid"/>
<result property="oname" column="oname"/>
<result property="num" column="num"/>
<collection property="users" javaType="com.lpc.entity.Users" select="com.lpc.dao.UsersMapper.selectByPrimaryKey"
column="uid"/>
</resultMap>

<select id="findById" parameterType="int" resultMap="rm">
select * from orders where oid=#{oid}
</select>

一对多: 用户: collection

1
2
3
4
5
6
7
8
实体类设计:
public class Users {
private Integer uid;
private String uname;
private Integer age;

// 一个用户有 多个订单
private List<Orders> ordersList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<Users> findAll();

<resultMap id="uu" type="users">
<id property="uid" column="uid"/>
<!--订单信息-->
<collection property="ordersList" ofType="orders">
<id property="oid" column="oid"/>
</collection>
</resultMap>

<select id="findAll" resultMap="uu">
select u.*,o.*
from users u,orders o
where u.uid=o.uid
</select>

ofType`:指定集合里面元素的类型

多对多: 老师—职务- 职务角色表

​ 一个老师可以担任 多个职位(如: 副校长,教务主任) 等 教务主任兼副校长

​ 一个角色也可以被多个人担任 如有3个副校长

​ 老师 和 职务 之间就是多对多关系

1
2
3
4
5
CREATE TABLE `teacher`  (
`tid` int(11) NOT NULL AUTO_INCREMENT,
`tname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`tid`) USING BTREE
)
1
2
3
4
5
CREATE TABLE `position`  (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`pname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
)
1
2
3
4
5
6
CREATE TABLE `teacher_position`  (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tid` int(11) NOT NULL,
`pid` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
)

做好三张表的数据关系

1
2
3
4
5
6
7
8
9
public class Teacher {
private Integer tid;
private String tname;
List<Position> positions;

public class Position {
private Integer pid;
private String pname;
private List<Teacher> teachers;

多对多 即是 两个 一对多

查询 老师有多少个职位

老师的一对多

1
2
3
4
5
6
7
8
9
10
11
12
13
List<Teacher> findAll();

<resultMap id="tt" type="teacher" autoMapping="true">
<id property="tid" column="tid"></id>
<collection property="positions" column="tid" ofType="position">
<id property="pid" column="pid"/>
</collection>
</resultMap>
<select id="findAll" resultMap="tt">
select t.tid, t.tname,p.pid,p.pname
from teacher t,teacher_position tp,position p
where t.tid=tp.tid and tp.pid=p.pid
</select>

13 自动映射

自动映射在 resultMap 中 讲解

设置

settings –> autoMappingBehavior

1
2
指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
NONE, PARTIAL, FULL 默认值是: PARTIAL

14 延迟加载

​ resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

延迟加载 要分开写:

使用association实现延迟加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 查询订单关联查询用户,用户信息按需延迟加载 的 resultMap定义 -->
<resultMap type="com.mybatis.entity.Orders" id="ordersUserLazyLoading">
<!--对订单信息进行映射配置 -->
<id column="id" property="id"/>
<result column="user_id" property="userid"/>
<result column="number" property="number"/>
<result column="createtime" property="createTime"/>
<result column="note" property="note"/>
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
column:订单信息中关联用户信息查询的列,是user_id
关联查询的sql理解为:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username,
(SELECT sex FROM USER WHERE orders.user_id = user.id)sex
FROM orders
-->
<association property="user" javaType="com.mybatis.entity.User" select="findUserById" column="user_id"/>
</resultMap>
<!-- 根据Id查询用户,用于测试延迟加载 -->
<select id="findUserById" parameterType="int" resultType="com.mybatis.entity.User" >
select * from t_user where id=#{id}
</select>
<!-- 查询订单关联用户,用户信息延迟加载 -->
<select id="findOrdersUserLazyLoading" resultMap="ordersUserLazyLoading">
select * from orders
</select>
1
2
3
4
5
6
public interface OrdersCustomMapper {
/**查询订单,关联查询用户,用户按需延迟加载*/
public List<Orders>findOrdersUserLazyLoading();
/**根据Id查询用户(这个方法本应该放在UserMapper类的,测试方便先放在这)*/
public User findUserById(int id);
}

如果只查询 订单 相关的信息 是不会查询 用户的信息.

1
2
3
4
5
6
7
<!-- 全局参数的配置 -->
<settings>
<!--打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将积极加载改为消极加载及按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

使用 collection 延迟加载

用户查找多个订单

1
2
3
4
5
6
7
8
9
10
11
<resultMap type="com.yutouxiuxiu.model.Person" id="selectOrderByPersonIdLazyRM" extends="personRM">
<!–
column - 主SQL语句查询的结果集的某一字段作为参数传给子SQL语句
select - 子SQL语句 –>
<collection property="orderList" column="person_id" select="com.yutouxiuxiu.Orders.selectOrderByPersonId">
</collection>
</resultMap>

<select id="selectOrderByPersonIdLazy" parameterType="int" resultMap="selectOrderByPersonIdLazyRM">
select * from person where person_id = #{personId}
</select>
1
2
3
4
<!– 一对多的时候的延迟加载的子SQL语句 –>
<select id="selectOrderByPersonId" parameterType="int" resultType="Order">
select * from orders o where o.person_id = #{personId}
</select>

15 resultType 和 resultMap 的区别

1
2
resultType 直接映射 POJO 中的数据, 底层也是 resultMap,用来处理简单的查询。
resultMap 要自己定义Map 映射关系,用来处理复杂的查询。

16 discriminator鉴别器

https://blog.csdn.net/qq_14810195/article/details/103307133

1
2
3
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>

17 动态Sql

1
2
3
4
5
6
if
where
choose (when, otherwise)
set
trim (where, set)
foreach

1 查询学生的 姓名 如果传入 姓名 按照姓名 模糊查询 如果没有传姓名 查询全部

if

1
2
3
4
5
6
<select id="findN" resultType="student">
select * from student
<if test="name != null">
where sname like concat('%',#{name},'%')
</if>
</select>

where

如果有两个值 年龄不为空 大于 年龄, 姓名不为空 ,模糊 司马 思考

1
2
3
4
5
6
7
8
9
10
11
<select id="findN1" resultType="student">
select * from student
<where>
<if test="name != null">
sname like concat('%',#{name},'%')
</if>
<if test="age != null">
and age>#{age}
</if>
</where>
</select>

choose when otherwise

如果传入的年龄 大于 20 到 30 查询姓张的 传入的年龄 大于 30 查询姓王的 否则 全查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findN2" resultType="student">
select * from student
<where>
<choose>
<when test="age &gt; 20 and age &lt; 30 ">
sname like '%王%'
</when>
<when test="age &gt; 30">
sname like '%司马%'
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</where>
</select>

set

1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

trim 模拟其他标签

mybatis的trim标签一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。

prefix自动在前面添加,prefixOverrides忽略最前面if的。suffixOverrides最后if的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<trim prefix="WHERE" prefixOverrides="AND">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</trim>

<update id="updateS" >
update student set
<trim prefix="set" suffixOverrides=",">
<if test="sname!=null">
sname=#{sname},
</if>
<if test="sex!=null">
sex=#{sex},
</if>
</trim>
where sid=#{sid}
</update>

foreach

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

1
2
3
4
5
6
7
8
<select id="findN3" parameterType="list" resultType="student">
select *
FROM student s
WHERE s.age in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>

18 MyBatis 缓存

lMyBatis系统中默认定义了两级缓存。

l一级缓存(local cache), 即本地缓存, 作用域默认 为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。

l本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.

一级缓存失效的情况

1
2
3
4
5
6
7
8
同一次会话期间只要查询过的数据都会保存在当 前SqlSession的一个Map
key:hashCode+查询的SqlId+编写的sql查询语句+参数
一级缓存失效的四种情况
1、不同的SqlSession对应不同的一级缓存
2、同一个SqlSession但是查询条件不同
3、同一个SqlSession两次查询期间执行了任何一次增 删改操作
4、同一个SqlSession两次查询期间手动清空了缓存

写案例演示

二级缓存

1
2
3
4
5
6
7
8
9
10
 二级缓存(secondlevelcache),全局作用域缓存  在 namespace  级别
二级缓存默认不开启,需要手动配置
MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口
二级缓存在SqlSession关闭或提交之后才会生效
使用步骤
1、全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
2、需要使用二级缓存的映射文件处使用cache配置缓存 • <cache />
3、注意:POJO需要实现Serializable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test5() throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession session1=sqlSessionFactory.openSession();
SqlSession session2=sqlSessionFactory.openSession();
// 得到数据
PositionMapper mapper1=session1.getMapper(PositionMapper.class);
PositionMapper mapper2=session2.getMapper(PositionMapper.class);
// 查询
Position p1=mapper1.selectByPrimaryKey(1);
System.out.println(p1);
// session1.commit();
System.out.println("========================================");
// 查询
Position p2=mapper1.selectByPrimaryKey(1);
System.out.println(p2);
}

不开启提交 走 二级缓存,

开启session1 的 提交 走缓存 sqlsession2的查询,使用了缓存,缓存的命中率是0.5。

1
2
3
影响缓存命中率的因素有很多,比如缓存太小,缓存的时间太短等都会影响缓存的命中率;但从本质上讲,影响缓存命中率的因素还是取决于缓存的粒度,缓存的粒度越小缓存的命中率也就越高,目前缓存粒度最小的是对象缓存(缓存粒度:在一些软件上,缓存粒度是可以自己设置的,而缓存粒度越小读取缓存的速度也就越快,当然这要视情况而论;比如说一个缓存粒度为512kb,而你只是需要读取一个4kb的缓存内容,那么由于缓存粒度关系,它会多读出508kb的缓存);

缓存命中率与读取缓存次数有关,如果不考虑缓存的清除等因素,读取缓存的次数越多缓存命中率也就越高(这里还涉及到了权重比)

测试update操作是否会刷新该namespace下的二级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test6() throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession session1=sqlSessionFactory.openSession();
SqlSession session2=sqlSessionFactory.openSession();
// 得到数据
PositionMapper mapper1=session1.getMapper(PositionMapper.class);
PositionMapper mapper2=session2.getMapper(PositionMapper.class);
// 查询
Position p1=mapper1.selectByPrimaryKey(1);
System.out.println(p1);
session1.commit();
mapper1.updateByPrimaryKey(new Position(1,"大校长"));
System.out.println("========================================");
// 查询
Position p2=mapper1.selectByPrimaryKey(1);
System.out.println(p2);
}

sqlSession2更新数据库,并提交事务后,sqlsession2StudentMapper namespace下的查询走了数据库,没有走Cache。

​ 二级缓存 脏读 测试 测试多表关联 1对1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 <resultMap id="rm" type="com.lpc.entity.Orders">
<id property="oid" column="oid"/>
<result property="oname" column="oname"/>
<result property="num" column="num"/>
<association property="users" column="uid" javaType="com.lpc.entity.Users"
select="com.lpc.dao.UsersMapper.selectByPrimaryKey"></association>
</resultMap>


<select id="findById" resultMap="rm">
select * from orders where oid=#{oid}
</select>


// 二级缓存 脏读 测试
@Test
public void test7() throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession session1=sqlSessionFactory.openSession();
SqlSession session2=sqlSessionFactory.openSession();

SqlSession session3=sqlSessionFactory.openSession();

// 得到数据
OrdersMapper mapper1=session1.getMapper(OrdersMapper.class);
OrdersMapper mapper2=session2.getMapper(OrdersMapper.class);
// 修改数据
UsersMapper mapper3=session3.getMapper(UsersMapper.class);

// 1
Orders o1=mapper1.findById(1);
System.out.println(o1);
System.out.println("=========================");
session1.commit();
// 2 修改数据
mapper3.updateByPrimaryKey(new Users(1,"张安的爹",200));
session3.commit();
// 3
Orders o2=mapper2.findById(1);
System.out.println(o2);
}

解决脏读的 办法

1
2
3
引入同一个缓存 空间

<cache-ref namespace="com.xmx.dao.OrdersDao"/>

二级缓存 使用建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
是否应该使用二级缓存?


那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项:

1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。

2. insert,update,delete操作会清空所在namespace下的全部缓存。

3. 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。

4. 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。

如果你遵守二级缓存的注意事项,那么你就可以使用二级缓存。

但是,如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。

整合 第三方缓存 Ehcache

https://blog.csdn.net/jinhaijing/article/details/84255107

https://tech.meituan.com/2018/01/19/mybatis-cache.html

1 添加ehcache依赖

1
2
3
4
5
6
<!-- ehcache依赖 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

2 配置 Ehcache 的核心文件

在src/main/resources目录下新增ehcache.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">

<!-- 指定数据在磁盘中的存储位置 -->
<diskStore path="D:\DEV_ENV\ehcache" />

<!-- 缓存策略 -->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

1
2
3
4
5
6
7
8
9
10
其中
maxElementsInMemory:内存中最大缓存对象数
maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大
eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
overflowToDisk:true表示当内存缓存的对象数目达到了
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120
timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性 值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限 期地处于空闲状态
timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EhCache将把它从缓存中清除。只有当eternal属性为false,该属性才有 效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有 意义
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。

指定

19 MyBatis Generator 逆向工程

1 导入 jar包

1
2
3
4
5
6
7
<!--    mybatis逆向工程-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>

2 配置核心文件 mbg.xml 放在项目的跟目录下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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="simple" targetRuntime="MyBatis3Simple">
<!-- 生成没有注释的bean-->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>

<!-- 配置数据库连接信息-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/tt?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC&amp;zeroDateTimeBehavior=convertToNull"
userId="root"
password="123456">
<property name="nullCatalogMeansCurrent" value="true" />
</jdbcConnection>

<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>

<!-- 指定javaBean生成的位置-->
<javaModelGenerator targetPackage="com.lpc.entity" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>


<!-- 指定sql映射文件生成的位置-->
<sqlMapGenerator targetPackage="com.lpc.dao" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>


<!-- 指定dao接口生成的位置,mapper接口-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.lpc.dao" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>


<!-- 指定每个表的生成策略-->
<table tableName="position" domainObjectName="Position"></table>

</context>
</generatorConfiguration>

3 测试生成

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TT {
public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mbg.xml");
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);
}
}

20 MyBatis 分页插件 PageHelper

pageHelper 中文 官网文档

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

1 在 依赖中引入 pageHelper 的插件

1
2
3
4
5
6
7
<!--    引入pageHelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>

2 在核心配置文件中 配置插件。

1
2
3
4
5
6
7
8
9
<!--    plugins标签要放在typeAliases标签后面-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 分页合理化,如果pageNum > pages,就让他查询最后一页。
如果pageNum < 0,就查询第一页-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>

3 用PageHelper 设置

1
2
3
4
5
6
7
// 传入第几页 和每页有几条数据
PageHelper.startPage(page,5);
// startPage 紧跟的查询就是分页查询
List<Employee> emps=employeeService.findAll();
// 用pageInfo包装list 连续显示5页 如 1 2 3 4 5
PageInfo pageInfo=new PageInfo(emps,5);

4 pageHelper 各个参数的意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总记录数
private long total;
//总页数
private int pages;
//结果集
private List<T> list;
//前一页
private int prePage;
//下一页
private int nextPage;
//是否为第一页
private boolean isFirstPage = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;

22 辅助 IDEA 中定制 模板