MyBatis

MyBatis半自动框架主要解决了Hibernate全自动框架的黑盒操作,不支持对sql语句不能高定制的问题

mybatis下载

使用MyBatis

不使用接口式编程

1、引入依赖:image-20220206171143386

2、创建一个数据库映射类(bean)

3、实现

每一次都需要获得sqlSession对象,sqlSession相当于程序与数据库的一次会话,使用完后需要关闭,sqlSession对象通过SqlSessionFactory(数据库会话工厂)对象获得,SqlSessionFactory对象通过SqlSessionFactoryBuilder对象构建

每次需要查询数据库时,使用sqlSession对象的selectOne(选择一个sql语句)方法,其有两个参数(一为sql语句的唯一标识,二为sql语句需要的参数)


测试类

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
public class MybatisTest {
/**
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
* 2、sql映射文件配置了每一个sql,以及sql的封装规则
* 3、将sql映射文件注册在全局配置文件中
* 4、代码:
* (1)根据全局配置文件得到SqlSessionFactory对象
* (2)使用SqlSessionFactory,获取到sqlSession对象,使用这个对象进行增删改查
* 一个sqlSession代表和数据库的一次会话,用完需要关闭
* (3)使用sql语句的唯一标识来告诉MyBatis要执行哪个sql语句
*
* @throws IOException
*/
@Test
public void test() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//获取sqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* 参数1:sql语句的唯一标识
* 参数2:执行sql要调用参数
*/
try {
Employee employee = sqlSession.selectOne("mapper.EmployeeMapper.selectEmp", 1);
System.out.println(employee.getLastName());
}finally {
sqlSession.close();
}
}
}

EmployeeMapper.xml(sql语句的映射文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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.EmployeeMapper">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型。会自动映射 (a_b => aB)
#{id}:从传递来的参数中取出id值
-->
<select id="selectEmp" resultType="bean.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>

mybatis-config.xml(mybatis全局配置文件)

用于配置数据源、注册sql映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/the_test_for_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!--将写好的sql映射文件注册到全局配置文件中-->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>



使用接口式编程

1、创建EmployeeMapper接口和对应的方法

2、EmployeeMapper.xml(sql语句的映射文件)中的命名空间(namespace)需要改成EmployeeMapper的全类名,select标签的id需要改成对应的方法的方法名;这样就将sql映射文件与接口相对应起来了

3、使用sqlSession对象的getMapper方法获得EmployeeMapper接口,调用接口的方法进行操作数据库

image-20220322092912687


测试类

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
public SqlSessionFactory getSqlSessionFactory() throws IOException{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void test1() throws IOException {
//1、获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

//2、获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();

try {
//3、获取接口的实现类对象
//会为接口自动创建一个代理对象,代理对象执行增删改查
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

Employee employee = mapper.getEmpById(1);

System.out.println(employee.getLastName());
}finally {
sqlSession.close();
}
}

EmployeeMapper接口

1
2
3
4
5
6
7
8
package dao;

import bean.Employee;

public interface EmployeeMapper {
public Employee getEmpById(Integer id);
}

EmployeeMapper.xml(sql语句的映射文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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.EmployeeMapper">
<!--
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型。会自动映射 (a_b => aB)
#{id}:从传递来的参数中取出id值


public Employee getEmpById(Integer id);
-->
<!--select标签的id需要改成接口中方法的方法名-->
<select id="getEmpById" resultType="bean.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>

总结:

  • SqlSession代表喝数据库的一次会话,用完必须关闭

  • SqlSession和connection一样都是非线程安全的,每次使用都要获取新的对象

  • mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象

  • 两个重要的配置文件:

    • mybatis全局配置文件:包含数据库连接池信息,事务管理器信息等……

    • sql映射文件:保存了每一个sql语句的映射信息


MyBatis全局配置文件

全局配置文件配置顺序:

properties => settings => typeAliases => typeHandlers => objectFactory => objectWrapperFactory => reflectorFactory => plugins => environments => databaseIdProvider => mappers

<properties>

1
2
<!--mybatis可以使用properties来引入外部properties配置文件的内容-->
<properties resource="类路径下的资源" url="引入网络路径或者磁盘路径下的资源 "></properties>

<settings>

1
2
3
4
5
<!--settings包含很多重要的设置项
setting用来设置每一个设置项-->
<settings>
<setting name="mapUpderscoreToCamelCase" value="是否开启自动驼峰命名法映射"></setting>
</settings>

<typeAliases>

1
2
3
4
5
6
7
8
9
<!--别名处理器,可以为我们的java类型起别名,别名不区分大小写-->
<typeAliases>
<typeAlias type="指定要其别名的类的全类名;默认名是类名小写" alias="指定新的别名"/>

<!--批量起别名,可以为某个包下的所有类批量起别名-->
<package name="指定包名(为当前以及下面所有的包都起一个默认别名)"/>

<!--可以使用@Alias注解为某个类型指定新的别名-->
</typeAliases>

<typeHandlers>

1
2
<!--架起数据库和java程序的连接-->
<typeHandlers></typeHandlers>

<plugins>

1
2
<!---->
<plugins></plugins>

<enviroments>

1
2
3
4
5
6
7
8
<!--环境,mybatis可以配置多种环境
environment:配置一个具体的环境信息,必须有transactionManager标签和dataSource标签-->
<enviroments default="选择某一个环境"> <!--可以在不同的情况下选择不同的环境,比如开发或者测试两个不同的情况-->
<enviroment id="环境的唯一标识">
<transactionManager type="事务管理器的类型(JDBC/MANAGED)"/>
<dataSource></dataSource>
</enviroment>
</enviroments>

<databaseIdProvider>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--根据你所指定的数据库厂商,提供对其他数据库厂商的sql语句支持
type="DB_VENDOR":作用是得到数据库厂商的标识,mybatis根据数据库厂商标识执行不同的sql
-->
<databaseIdProvider type="DB_VENDOR">
<!--为不同的数据库厂商起别名-->
<pro name="Oracle" value="oracle"/>
</databaseIdProvider>

配置之后:
<!--sql映射文件
databaseId="oracle"
配置环境environment时选择oracle数据库,即使还有一个select标签id值一样,也会选择与数据库相对应的select标签-->
<select id="getEmpById" resultType="bean.Employee" databaseId="oracle">
select * from tbl_employee where id = #{id}
</select>
<select id="getEmpById" resultType="bean.Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
</select>

<mappers>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--将sql映射文件注册到全局配置中-->
<mappers>
<!--mapper:注册一个sql映射
resource:引用类路径下的sql映射文件
url:引用网络或磁盘路径下的sql映射文件
class:引用(注册)接口
1、有sql映射文件,名必须和接口同名,且放在接口的同名目录下
2、没有sql文件,所有的sql都是使用注解写在接口上-->
<mapper resource="EmployeeMapper.xml"/>
<mapper class="dao.EmployeeMapper"/>

<!---批量注册-->
<package name="包名"/>
</mappers>

1
2
3
4
5
6
7
8
9
10
11







public interface EmployeeMapper {
@Select("select * from tbl_employee where id = #{id}")
public Employee getEmpById(Integer id);
}

多环境下:

在开发环境和测试环境不同情况下,可以在enviroments标签中配置不同的环境下所需要使用的事务管理器和数据源,然后databaseIdProvider标签使用给不同的厂商起不同的别名,在sql映射文件中的select标签或其他sql语句标签中使用databaseId属性表明该sql语句适用的是哪一个数据库厂商

MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。


MyBatis映射文件

MyBatis的CRUD操作

insert语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--插入sql语句-->
<!--parameterType:可以省略
useGeneratedKeys:是否开启自增主键,仅对 insert 和 update 有用,这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键

keyProperty:返回自增的主键到参数中的某个属性,需要搭配useGeneratedKeys使用,仅对 insert 和 update 有用,唯一标记一个属性,获得的主键值将会赋值给该属性-->
<insert id="addEmp" parameterType="bean.Employee" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
<!--#{}表示从传递来的对象中取出大括号内的属性-->
</insert>

<!-- 对于一些不提供主键自增功能的数据库,如Oracle,需要使用selectKey标签来获取主键的值 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into USER(id,user_name,password)
values(#{id},#{userName},#{password})
<!-- order:after 在插入之后再赋值 -->
<selectKey resultType="int" keyProperty="id"
order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>

update语句

1
2
3
4
<!--更新sql语句-->
<update id="updateEmp">
update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender} where id=#{id}
</update>

delete语句

1
2
3
4
5
6
7
8
<!--删除sql语句-->
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
</delete>
<!--主线程中增删改操作需要手动提交事务-->
sqlSession.commit();
<!--若opensession()方法中的参数为true则会自动提交事务-->
sqlSessionFactory.openSession(true)

select语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- resultType 返回值类型 -->
<select id="getEmpById" resultType="bean.Employee">
select * from tbl_employee where id = #{id}
</select>



<!--select返回list类型
resultType:如果返回的是一个集合,要写集合中的元素类型-->
public List<Employee> getEmpByLastNameLike(String lastName); (java)

<select id="getEmpByLastNameLike" resultType="bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>



<!--select返回Map<Integer,Employee>类型
resultType:如果返回的是一个集合,要写集合中的元素类型-->
@MapKey("id") <!--表明以id作为map的key-->
public Map<Integer,Employee> getEmpByLastNameLike(String lastName); (java)
<select id="getEmpByLastNameLike" resultType="bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>

关联映射

类之间关系

image-20220326093646175

数据库表之间关系

image-20220326093710254

resultMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--自定义某个javaBean的封装规则
type:自定义规则的java类型
id:唯一标识-->
<resultMap type="bean.Employee" id="MyEmp">
<!--指定主键列的封装规则
column:指定哪一列
property:指定对应的javaBean属性名-->
<id column="id" property="id"/>

<!--定义普通列封装规则-->
<result column="last_name" property="lastName"/>

<!--其他不指定的列如果列名一致会自动封装;如果使用resultMap建议全写-->
</resultMap>
<!--resultMap:自定义结果集映射规则
resultMap不能跟resultType一起使用-->
<select id="getEmpByMap" resultMap="MyEmp" >
select * from tbl_employee where id = #{id}
</select>

一对一关系映射

方式一:使用自动映射处理一对一关系,也就是通过别名自动将值匹配到对应的字段上

1
2
3
4
5
6
7
8
9
<select id="findUserAndShoopingCartById" 
resultType="com.mybatis.entity.User">
select u.id, u.user_name userName, u.password,
s.cart_id "shoppingCart.id",
s.price "shoppingCart.price"
from User u
left join shopping_cart s on u.id = s.user_id
where u.id = #{id}
</select>

方式二:在XML映射文件中配置结果映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--
关联查询
场景一:查询Employee的同时查询员工对应的部门
-->

<!----------------------方法一------------------------->
<resultMap id="MyEmp" type="bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!--dept就是Employee类型中的Department属性的属性名-->
<!--id和deptName是Department类型的成员变量名-->
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.deptName"/>
</resultMap>

<select id="getEmpAndDeptById" resultMap="MyEmp">
<!--这里由于两张表都有id属性 所以需要给他们起别名来区分-->
select e.id id, e.last_name last_name, e.gender gender ,e.email email ,e.d_id d_id
d.id did d.dept_name dept_name from tbl_employee e,tbl_dept d
where e.id = d.id and e.id = #{id}
</select>

方式三:association元素用于和一个复杂的类型进行关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    <!--使用association定义单个对象的封装规则-->
<resultMap id="MyEmp" type="bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- association可以指定联合的javaBean对象
property:指定哪个属性是联合的对象
javaType:指定这个属性对象的类型(不能省略)
-->
<association property="dept" javaType="bean.Department">
<id column="d_id" property="id"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>

<select id="getEmpAndDeptById" resultMap="MyEmp">
<!--这里由于两张表都有id属性 所以需要给他们起别名来区分-->
select e.id id, e.last_name last_name, e.gender gender ,e.email email ,e.d_id d_id
d.id did d.dept_name dept_name from tbl_employee e,tbl_dept d
where e.id = d.id and e.id = #{id}
</select>

方式四: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
28
29
30
31
32
33
34
35
36
37
38
39
40
<!--
分步查询
场景:现实开发中不止会有EmployeeMapper.xml 也会有DepartmentMapper.xml
我们可以使用association进行分步查询
1、先按照员工id查询员工信息
2、根据查询员工信息中的d_id值去部门表查出部门信息
-->
<!------------------DepartmentMapper.xml-------------------->
<mapper namespace="dao.DepartmentMapper">
<select id="getDeptById" resultType="bean.Department">
select * from tbl_department where id = #{id}
</select>
</mapper>

<!------------------EmployeeMapper.xml--------------------->
<resultMap id="MyEmp" type="bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- association可以指定联合的javaBean对象
property:指定哪个属性是联合的对象
select:表明当前属性是调用select指定的方法查出的结果(就是DepartmentMapper.xml sql映射文件绑定的方法)
column:指定将哪一列的值传递给这个方法

流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,然后封装给peoperty所指定的属性
-->
<association property="dept"
select="bean.Department.getDeptById"
column="d_id">
</association>
</resultMap>
<select id="getEmpAndDeptById" resultMap="MyEmp">
select * from tbl_employee where id = #{id}
</select>
<!--
select e.id id, e.last_name last_name, e.gender gender ,e.email email ,e.d_id d_id
d.id did d.dept_name dept_name from tbl_employee e,tbl_dept d
where e.id = d.id and e.id = #{id}
-->

注:我们每次查询Employee对象的时候,Department对象都会一并查询出来

但是我们不一定去使用Department对象,这样就造成资源浪费,我们可以在分段查询的基础上在全局配置文件中加上两个配置

1
2
3
4
5
6
<!-- 使用延迟加载(懒加载)-->
<!-------------------------mybatis-config.xml--------------------------->
<settrings>
<setting name="lazyLoadingEnabled" value="true"/> <!--懒加载,所关联的值在使用时候才加载-->
<setting name="aggressiveLazyLoading" value="false"/> <!--侵略性懒加载,在需要任何一个属性时候就加载全部属性-->
</settrings>

一对多关系映射

类之间关系

image-20220326093537007

数据库表之间关系

image-20220326093611193

方式一:与association类似,集合的嵌套结果映射就是指通过一次SQL查询得到所有的结果

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
<!--场景二:查询每个部门的同时查询出该部门的员工列表-->
public class Department{
...
private List<Employee> emps;
...
}
<!-----------方法一:使用collection定义关联集合类型的属性的封装规则 ------------>
<!------------------DepartmentMapper.xml-------------------->
<resultMap id="MyDept" type="bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<!-- collection定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型-->
<collection porperty="emps" ofType="bean.Employee">
<!--定义集合中元素的封装规则-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!--为什么这里不用查Department呢?因为这里前面Department的封装规则已经表明了当前查出来的Department是与里面封装的这个Employee是有关联的-->
<!-- did dept_name eid last_name gender email -->
</collection>
</resultMap>

<select id="getDeptById" resultMap="MyDept">
select d.id did d.dept_name dept_name,
e.id eid, e.last_name last_name, e.gender gender ,e.email email
from tbl_dept d
left join tbl_employee e
on d.id = e.d_id
where d.id=#{id}
</select>

方式二:集合的嵌套查询同样会执行额外的SQL查询

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
<!-----------方法二:分步查询------------>
<!--流程:
1、先查出Department的id和dept_name
2、再调用Employee中的getEmpByDeptId方法查出员工列表
3、通过collection标签将其封装到Dpartment的emps属性中
-->
<!------------------DepartmentMapper.xml-------------------->
<resultMap id="MyDept" type="bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<!-- collection定义关联集合类型的属性的封装规则
select:指定需要使用哪个方法查询
column:指定需要传递的参数-->
<collection porperty="emps"
select="bean.Employee.getEmpByDeptId"
column="id"></collection>
<!-- 扩展:
多列值传递过去:将多列值封装map传递
column="{key=columnName,...}" 如 column="{deptId=id,deptName=dept_name}"
延迟加载:fetchType="lazy" 也可以在全局中设置延迟加载
eager:立即加载,可以不改变全局设置,直接在这里设置参数-->
</resultMap>

<select id="getDeptByIdStep" resultMap="MyDept">
select id dept_name from tbl_dept where id=#{id}
</select>

<!-------------------EmployeeMapper.xml---------------------->
public interface EmployeeMapper{
...
public List<Employee> getEmpByDeptId(integer deptId){...};
...
}

<select id="getEmpByDeptId" resultType="bean.Employee">
select * from tbl_employee where d_id=#{deptId}
</select>
1
2
3
4
<!--扩展:鉴别器 具体使用方法在网上看-->
<discriminator javaType="">
<case></case>
</discriminator>

鉴别器映射

有时一个单独的数据库查询会返回很多种不同数据类型的结果集。discriminator鉴别器元素可以用来处理这种情况

数据库表结构:image-20220326095953466 使用一张表,以某个字段标识子类

方式一:使用discriminator鉴别器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap type="com.mybatis.entity.Employee" id="empMap">
<id colum = "id" property="id"/>
<result colum = "name" property="name"/>
<discriminator column="employee_type"
javaType="String">
<!-- value:指定额外字段的取值 -->
<case value="HE" resultMap="com.entity.HourlyMap"/>
<case value="SE" resultMap="com.entity.SalaryMap"/>
</discriminator>
</resultMap>

<select id="findAllEmps" resultMap="employeeMap">
select * from employee
</select>

方式二:使用extends继承父类映射

1
2
3
4
5
6
7
8
9
<resultMap type="com.mybatis.entity.Employee" id="empMap">
<id colum = "id" property="id"/>
<result colum = "name" property="name"/>
</resultMap>

<!-- extends属性集成父类的映射 -->
<resultMap type="com.mybatis.entity.HourlyMap" id="hourMap" extends="empMap">
<result property="rate" column="rate"/>
</resultMap>

动态SQL

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!-- 查询员工
要求:Employee类型中携带了哪个字段,查询条件就带上这个字段的值 -->
public interface EmployeeMapper{
...
public List<Employee> getEmpByConditionIf(Employee employee);
...
}

<!-------------------EmployeeMapper.xml---------------------->
<!------- if判断语句 --------->
<select id="getEmpByConditionIf" resultType="bean.Employee">
select * from tbl_employee
where
<!-- test:判断表达式(OGNL表达式)
从参数中取值进行判断
特殊符号使用转义字符(参考文档)-->
<if test="id!=null">
id=#{id}
</if>
<if test="last_name!=null and lastName!=''">
and last_name like #{lastName}
</if>
<if test="email!=null">
and email=#{email}
</if>
<!-- ognl会进行字符串与数字的转换 -->
<if test="gender==1 or gender==0">
and gender=#{gender}
</if>
</select>


<!-- 内置参数:
_databaseId 如果配置了databaseIdProvider,就是代表当前数据库的别名-->
<select id="getEmpsByConditionDatabaseId" resultType="bean.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
</if>
<if test="_databaseId=='oracle'">
select * from employees
</if>
</select>

where语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!------- where条件语句(封装查询条件)--------->
<!--where标签将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或or去掉
where只会去掉第一个多出来的and或or标签,比如 and id=1 and name=jjw;但不能去掉最后一个and或or,比如 id=1 and name=jjw and-->
<select id="getEmpByConditionWhere" resultType="bean.Employee">
select * from tbl_employee
<where>
<!-- test:判断表达式(OGNL表达式)
从参数中取值进行判断
特殊符号使用转义字符(参考文档)-->
<if test="id!=null">
id=#{id}
</if>
....
</where>
</select>

trim 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!------- trim语句 --------->
<!-- where标签不能解决and在最后的情况 -->
<select id="getEmpByConditionTrim" resultType="bean.Employee">
select * from tbl_employee
<trim prefix="where" suffixOverrides="and">
<!-- trim截取标签
prefix="" 前缀,trim标签是整个字符串拼后的结果,prefix给拼串后的整个字符串加一个前缀
prefixOverride="" 前缀覆盖,去掉整个字符串前面多余的字符,可以有多个选择 例如 "and|or"
suffix="" 给拼串后的整个字符串加一个后缀
suffixOverrides="" 后缀覆盖,去掉整个字符串后面多余的字符,可以有多个选择 例如 "and|or" -->
<if test="id!=null">
id=#{id} and
</if>
....
</trim>
</select>

choose 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!------- choose条件选择语句 --------->      
<!-- 一个choose元素至少包含一个when子元素
一个choose元素包含 0 或 1 个otherwise子元素
choose元素可以实现类似于 if else 语句的作用 -->
<select id="getEmpByConditionChoose" resultType="bean.Employee">
select * from tbl_employee
<!-- 有什么就查什么 -->
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
....
<!-- 当前面所有的when子元素都不满足条件时,则执行otherwise中的条件 -->
<otherwise>
<!-- 其他条件 -->
</otherwise>
</choose>
</where>
</select>

set 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!------- set语句(封装修改条件) --------->    
<update id="updateEmpByConditionSet">
update tbl_employee
<set>
<if test="lastName!=null">
last_name=#{lastName}, <!--set标签会将多余的 “,” 去除-->
</if>
....
</set>
<!--
<trim prefix="set" suffixOverrides=",">
<if test="lastName!=null">
last_name=#{lastName},
</if>
....
</trim>
-->
where id=#{id}
</update>

foreach 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!------- foreach语句(封装修改条件) --------->     
<select id="getEmpByConditionForeach" resultType="bean.Employee">
select * from tbl_employee where id in
<!--
collection:指定要遍历的集合
item:将当前遍历出的元素赋值给变量
separator:每个元素之间的分隔符
open、close:拼接上开始 或 结束字符
index:索引,当参数为Map类型时,为Map的key
-->
<foreach collection="ids" item="item_id" separator="," open="(" close=")">
#{item_id}
</foreach>
</select>

<!-- 使用foreach实现批量插入 -->
<insert id="addEmps">
insert into tbl_employee(last_name,email,gender,d_id)
values
<foreach cllection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id })
</foreach>
</insert>

bind 标签

1
2
3
4
5
6
<!-- bind标签
bind 可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量值,相当于定义了一个新的变量方便使用-->
<bind name="_lastName" value="'_'+lastName+'%'"/>
<select id="getEmpsByConditionBind" resultType="bean.Employee">
select * from tbl_employee where last_name like #{_lastName}
</select>

抽取可重用的sql片段

1
2
3
4
5
6
7
8
9
<!--抽取可重用的sql片段,方便后面引用-->        
<sql id="insertColumn">
id, last_name, email
</sql>

insert into tbl_employees(
<!--引用外部定义的sql语句-->
<include refid="insertColumn"></include>
) ...

MyBatis参数处理

单个参数:mybatis不会做特殊处理

​ #{参数名}:取出参数

多个参数:mybatis会做特殊处理,多个参数会被封装成一个map,#{}就是从map中获取指定的key的值

​ #{param1}…#{paramN} 或者 参数的索引也可以

1
2
3
4
5
6
7
<select id="getEmpById" resultType="bean.Employee" databaseId="mysql" >
select * from tbl_employee where id = #{agr0} and last_name = #{arg1}
</select>

<select id="getEmpById" resultType="bean.Employee" databaseId="mysql" >
select * from tbl_employee where id = #{param1} and last_name = #{param2}
</select>

命名参数:明确指定封装参数时map的key

​ #{命名}

1
public Employee getEmpById(@Param("id") Integer id,@Param("lastName") String lastName);

POJO

如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo

​ #{属性名}:取出传入的pojo的属性值

Map

如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map

1
2
3
4
5
6
public Employee getEmpByMap(Map<String,Object> map);

Map<String, Object> map = new HashMap<>();
map.put("id",2);
map.put("lastName","jjw");
Employee employee = mapper.getEmpByMap(map);
1
2
3
<select id="getEmpByMap" resultType="bean.Employee" >
select * from tbl_employee where id = #{id}
</select>

TO

如果多个参数不是业务模型中的数据,没有对应的pojo,经常使用,推荐使用一个TO数据传输对象,如:

1
2
3
4
5
public class Page{
int index;
int size;
...
}

特别注意:如果是Collection(List、Set)类型或者是数组,也会特殊处理

#{conllection[0]} /(当参数为list)#{list[0]} /(当参数为数组)#{array[0]}

MyBatis参数获取:

​ #{}:可以获取map中的值或者pojo对象属性的值;

​ ${}:可以获取map中的值或者pojo对象属性的值;

区别:

​ #{}:是以预编译的形式,将参数设置到sql语句中

​ ${}:取出的值直接拼装到sql语句中;会有安全问题


MyBatis分页插件

1、

普通Java项目中引入依赖 image-20220330090218686

maven项目中引入pagehelper依赖

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.0.0</version>
</dependency>

2、找到mybatis配置文件,在此文件中加入插件配置如下

1
2
3
4
5
6
7
8
9
10
11
<!-- 插件配置 -->
<plugins>
<!-- 配置分页插件 -->
<!-- 配置拦截器 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<!-- <property name="dialect" value="mysql"/> -->
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<!-- <property name="returnPageInfo" value="always"/> -->
</plugin>
</plugins>

3、调用PageHelper类中的方法,实现分页的功能

1
2
3
4
//参数一:页号
//参数二:一页包含多少条数据
PageHelper.startPage(2,3);
//下面的第一个查询方法,会按照上面的分页方式查询

分页查询时,查询方法返回的List对象实际上是Page<E>类型,如果需要取得分页信息,需要将结果强制类型转换为Page<E>类型

1
2
3
4
5
Page<User> userPages = (Page<User>)users;
System.out.println(userPages.getTotal());//总条数
System.out.println(userPages.getPageNum());//当前页数
System.out.println(userPages.getPageSize());//每页条数
System.out.println(userPages.getPages());//总页数

MyBatis缓存机制

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

一级缓存和二级缓存

  • 默认情况下只有一级缓存开启(也称为本地缓存)
  • 二级缓存需要手动开启和配置,是基于namespace级别的缓存
  • MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存

一级缓存

image-20220329092811934

sqlSession级别的缓存

作用:与数据库同一次会话期间查询到的数据会放在一级缓存中,是一直开启的,无法关闭

但增删改操作不会将数据放入缓存中,还会将缓存清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testCache throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
//一级缓存中使用Map存储查询结果中的对象
//key值: statementid+参数 (statementId 就是接口的全限定名加上方法名)
//value值:查询结果中的对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp1 = mapper.getEmpById(1);
Employee emp2 = mapper.getEmpById(1); //mapper会去一级缓存中查找,并返回上一次返回的对象

System.out.println(emp1 == emp2) //true
}
}

一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要向数据库发出查询)

  1. sqlSession不同
  2. sqlSession相同,查询条件不同
  3. sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对数据有影响)
  4. sqlSession相同,手动清除了一级缓存
  • ```java
    session.clearCache();
    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
    49




    **一级缓存的生命周期**

    - MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象;当会话结束时,SqlSession对象也一并释放掉
    - 如果SqlSession调用了close()方法,会释放掉一级缓存,一级缓存将不可用
    - 如果SqlSession调用了clearCache(),会清空缓存,但是该对象仍可使用
    - **在SqlSession中执行了任何一个update操作、insert操作、delete操作,都会清空缓存**,但是该对象可以继续使用



    **MyBatis中,如果以下条件都一样,那么就认为它们是完全相同的两次查询**

    - 传入的 statementId (statementId 就是接口的全限定名加上方法名)
    - 查询时要求的结果集中的结果范围
    - 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串
    - 传递给java.sql.Statement要设置的参数值



    ### 二级缓存

    namespace级别的缓存,**也就是每个sql映射文件对应一个自己的二级缓存**

    **工作机制:**

    1. 一个会话,查询一条数据,这个数据就会呗放在当前会话的一级缓存中
    2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的查询信息就可以参照二级缓存
    3. 不同 namespace 查出的数据会放在自己对应的缓存中(map)

    注:只有会话提交或关闭之后,一级缓存中的数据才会转移到二级缓存中

    顺序:二级缓存->一级缓存->数据库

    <img src="MyBatis/image-20220329102144695.png" alt="image-20220329102144695" style="zoom:80%;" />



    **使用:**

    1. 开启二级缓存配置 (需要放在 environments 标签之前)

    ```xml
    <!-------------------mabatis-config.xml------------------->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    </settings>
  1. XxxMapper.xml中配置使用二级缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<mapper namespace="mapper.EmployeeMapper">
<!--
eviction:缓存的回收策略
- LRU 最近最少使用的
- FIFO 先进先出策略
- SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK 弱引用,更积极的移除基于垃圾回收器状态和软引用规则的对象
默认使用的是LRU

flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,可以设置一个毫秒值

readOnly:是否只读
true:只读 mybatis认为所有从缓存中获取数据的操作都只是只读操作,不会修改数据 mybatis为了加快获取速度,直接就会将在缓存中的引用交给用户。不安全
false:非只读 mybatis认为获取的数据可能会被修改 mybatis会利用序列化和反序列化技术克隆一份新的数据给你。安全

size:缓存多少元素

type:指定自定义缓存的全类名(实现Cache接口)
-->
<cache eviction="" flushInterval="" readOnly="" size="" type=""></cache>
...
</mapper>
  1. 我们的POJO需要实现序列化接口

@Test

1
2
3
4
5
6
7
8
9
10
11
12
EmployeeMapper mapper1 = session1.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = session2.getMapper(EmployeeMapper.class);

Employee emp1 = mapper1.getEmpById(1);
System.out.println(emp1);
session1.close();

Employee emp2 = mapper2.getEmpById(1);
System.out.println(emp2);
session2.close();

//只会发送一次sql查询,因为第一次关闭数据库连接之后会利用序列化和反序列化技术克隆一份新的数据在二级缓存中

二级缓存中脏数据产生的原因

  • 通常情况下每个Mapper映射文件都有自己的二级缓存
  • 有些查询需要关联多张表
  • 涉及这多张表的增删改查操作通常不在一个映射文件中
  • 当数据有变化时,多表查询的缓存不一定会清空,这种情况下就会出现脏数据

例如:

user二级缓存 order二级缓存
{userid=1,orderid={1,2,3}} {orderid=1}

如果这时候将order表中的这条数据删除,只会影响到order二级缓存中的数据,而不会影响到user二级缓存中的数据,造成数据脏读,我们可以让多个Mapper公用一个Cache缓存来解决问题

如果想让多个Mapper公用一个Cache缓存,可以使用<cache-ref namespace=””>元素配置参照缓存,这样不仅能减少配置项,还能解决脏读问题

image-20220329105735375

二级缓存适用场景

  • 以查询为主的应用中,只有尽可能少的增、删、改操作
  • 绝大多数都是单表操作时,很少出现相互关联的情况
  • 如关联的表比较少,可以通过参照缓存进行配置

扩展:

  1. 每个 select标签 都有 userCache属性

    • true:可以使用二级缓存
    • false:不使用二级缓存
  2. 每个增删改查标签都有 flushCache属性

增删改默认为true,即增删改之后就会清空一级和二级缓存,所以增删改之后再查询不会从缓存中拿,而是重新发送一次sql查询

  1. sqlSession.clearCache(),只是清除一级缓存中的数据

image-20220219164047000

ehcache缓存

Mybatis使用一二级缓存时采用的是map来存储数据,这样做过于简单,我们可以使用ehcache进行缓存

1、引入依赖:image-20220219165613257image-20220219174950268

2、在XxxMapper.xml文件中更改 cache标签 的type属性,即使用 org.mybatis.caches.ehcache.EhcachCache 作为二级缓存

image-20220219170820383

也可以更改一些参数(具体参考官方文档)

image-20220219170853013

3、增加ehcache.xml文件

参考:https://blog.csdn.net/tototuzuoquan/article/details/42873775

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

<diskStore path="D: \44\ehcache"/>
<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk= "1000000"
eternal="false"
overflowToDisk= "true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>